[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 {
|
type OfficialAccountType struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
DefaultDomainName string
|
||||||
UserConfig PowerWechatOfficialAccountUserConfigType
|
UserConfig PowerWechatOfficialAccountUserConfigType
|
||||||
TempQrExpireSeconds int
|
TempQrExpireSeconds int
|
||||||
PowerWechat *PowerWechatOfficialAccountType.OfficialAccount
|
PowerWechat *PowerWechatOfficialAccountType.OfficialAccount
|
||||||
@@ -40,12 +41,20 @@ type OfficialAccountType struct {
|
|||||||
*/
|
*/
|
||||||
func loadWechatSettingsFrom(rootCfg ConfigProvider) {
|
func loadWechatSettingsFrom(rootCfg ConfigProvider) {
|
||||||
sec := rootCfg.Section("wechat")
|
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.AppID = sec.Key("WECHAT_OFFICIAL_ACCOUNT_APP_ID").MustString("")
|
||||||
Wechat.OfficialAccount.UserConfig.AppSecret = sec.Key("WECHAT_OFFICIAL_ACCOUNT_APP_SECRET").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.RedisAddr = sec.Key("WECHAT_OFFICIAL_ACCOUNT_REDIS_ADDR").MustString("")
|
||||||
Wechat.OfficialAccount.UserConfig.MessageToken = sec.Key("WECHAT_OFFICIAL_ACCOUNT_MESSAGE_TOKEN").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("")
|
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)
|
Wechat.OfficialAccount.TempQrExpireSeconds = sec.Key("WECHAT_OFFICIAL_ACCOUNT_TEMP_QR_EXPIRE_SECONDS").MustInt(60)
|
||||||
// 扫码后,最长注册时间:默认24小时
|
// 扫码后,最长注册时间:默认24小时
|
||||||
Wechat.OfficialAccount.RegisterationExpireSeconds = sec.Key("WECHAT_OFFICIAL_ACCOUNT_REGISTERATION_EXPIRE_SECONDS").MustInt(86400)
|
Wechat.OfficialAccount.RegisterationExpireSeconds = sec.Key("WECHAT_OFFICIAL_ACCOUNT_REGISTERATION_EXPIRE_SECONDS").MustInt(86400)
|
||||||
@@ -87,8 +96,5 @@ func createPowerWechatApp(userConfig PowerWechatOfficialAccountUserConfigType) {
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("创建微信工具类 PowerWechat 失败,请检查 modules/setting/wechat.go ")
|
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 = Disable Self-Registration
|
||||||
disable_registration_popup = Disable user self-registration. Only administrators will be able to create new user accounts.
|
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
|
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 = Enable OpenID Sign-In
|
||||||
openid_signin_popup = Enable user sign-in via OpenID.
|
openid_signin_popup = Enable user sign-in via OpenID.
|
||||||
openid_signup = Enable OpenID Self-Registration
|
openid_signup = Enable OpenID Self-Registration
|
||||||
|
|||||||
@@ -306,6 +306,8 @@ federated_avatar_lookup_popup=启用 Federated Avatars 查找以使用开源的
|
|||||||
disable_registration=禁止用户自助注册
|
disable_registration=禁止用户自助注册
|
||||||
disable_registration_popup=禁用用户自助注册。只有管理员才能创建新的用户帐户。
|
disable_registration_popup=禁用用户自助注册。只有管理员才能创建新的用户帐户。
|
||||||
allow_only_external_registration_popup=仅允许通过外部服务注册
|
allow_only_external_registration_popup=仅允许通过外部服务注册
|
||||||
|
wechat_qr_signin=启用 微信二维码 登录
|
||||||
|
wechat_qr_signin_popup=启用通过 微信二维码 登录
|
||||||
openid_signin=启用 OpenID 登录
|
openid_signin=启用 OpenID 登录
|
||||||
openid_signin_popup=启用通过 OpenID 登录
|
openid_signin_popup=启用通过 OpenID 登录
|
||||||
openid_signup=启用 OpenID 自助注册
|
openid_signup=启用 OpenID 自助注册
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package official_account
|
package official_account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
Result "code.gitea.io/gitea/routers/entity"
|
Result "code.gitea.io/gitea/routers/entity"
|
||||||
wechat_official_account_service "code.gitea.io/gitea/services/wechat/official_account"
|
wechat_official_account_service "code.gitea.io/gitea/services/wechat/official_account"
|
||||||
"net/http"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// QrCheckCodeStatus 检查二维码扫描状态
|
// QrCheckCodeStatus 检查二维码扫描状态
|
||||||
@@ -31,7 +32,6 @@ func QrCheckCodeStatus(responseWriter http.ResponseWriter, request *http.Request
|
|||||||
Result.RespFailedIllegalWechatQrTicket.RespondJson2HttpResponseWriter(responseWriter)
|
Result.RespFailedIllegalWechatQrTicket.RespondJson2HttpResponseWriter(responseWriter)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用 WeChat Official Account Service 层,获取二维码扫码状态
|
// 调用 WeChat Official Account Service 层,获取二维码扫码状态
|
||||||
qrStatusVO, err := wechat_official_account_service.GetTempQRStatus(ticket)
|
qrStatusVO, err := wechat_official_account_service.GetTempQRStatus(ticket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ func Install(ctx *context.Context) {
|
|||||||
|
|
||||||
form.EnableOpenIDSignIn = setting.Service.EnableOpenIDSignIn
|
form.EnableOpenIDSignIn = setting.Service.EnableOpenIDSignIn
|
||||||
form.EnableOpenIDSignUp = setting.Service.EnableOpenIDSignUp
|
form.EnableOpenIDSignUp = setting.Service.EnableOpenIDSignUp
|
||||||
|
form.EnableWechatQRSignIn = true
|
||||||
form.DisableRegistration = setting.Service.DisableRegistration
|
form.DisableRegistration = setting.Service.DisableRegistration
|
||||||
form.AllowOnlyExternalRegistration = setting.Service.AllowOnlyExternalRegistration
|
form.AllowOnlyExternalRegistration = setting.Service.AllowOnlyExternalRegistration
|
||||||
form.EnableCaptcha = setting.Service.EnableCaptcha
|
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)
|
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
||||||
return
|
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_SIGNIN").SetValue(fmt.Sprint(form.EnableOpenIDSignIn))
|
||||||
cfg.Section("openid").Key("ENABLE_OPENID_SIGNUP").SetValue(fmt.Sprint(form.EnableOpenIDSignUp))
|
cfg.Section("openid").Key("ENABLE_OPENID_SIGNUP").SetValue(fmt.Sprint(form.EnableOpenIDSignUp))
|
||||||
cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(fmt.Sprint(form.DisableRegistration))
|
cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(fmt.Sprint(form.DisableRegistration))
|
||||||
|
|||||||
@@ -5,9 +5,6 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
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"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
@@ -15,6 +12,10 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"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/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@@ -167,6 +168,15 @@ func CheckAutoLogin(ctx *context.Context) bool {
|
|||||||
return false
|
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 渲染微信扫码登录页面
|
// SignInWechatQr 渲染微信扫码登录页面
|
||||||
func SignInWechatQr(ctx *context.Context) {
|
func SignInWechatQr(ctx *context.Context) {
|
||||||
if CheckAutoLogin(ctx) {
|
if CheckAutoLogin(ctx) {
|
||||||
@@ -177,14 +187,15 @@ func SignInWechatQr(ctx *context.Context) {
|
|||||||
RedirectAfterLogin(ctx)
|
RedirectAfterLogin(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SignInNavbarSetting(ctx)
|
||||||
|
|
||||||
ctx.Data["Title"] = ctx.Tr("sign_in")
|
ctx.Data["Title"] = ctx.Tr("sign_in")
|
||||||
ctx.Data["SignInWechatQrLink"] = setting.AppSubURL + "/user/login/wechat/official-account"
|
ctx.Data["SignInWechatQrLink"] = setting.AppSubURL + "/user/login/wechat/official-account"
|
||||||
ctx.Data["PageIsSignIn"] = true
|
ctx.Data["PageIsSignIn"] = true
|
||||||
ctx.Data["PageIsLogin"] = true
|
|
||||||
ctx.Data["PageIsWechatQrLogin"] = true
|
ctx.Data["PageIsWechatQrLogin"] = true
|
||||||
ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx)
|
|
||||||
|
|
||||||
_, err := GenerateWechatQr(ctx)
|
wechatQrTicket, wechatQrCodeUrl, err := auth_service.GetWechatQRTicket(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wechatQrFallbackLoginURL := "/user/login"
|
wechatQrFallbackLoginURL := "/user/login"
|
||||||
redirectTo := ctx.FormString("redirect_to")
|
redirectTo := ctx.FormString("redirect_to")
|
||||||
@@ -196,6 +207,10 @@ func SignInWechatQr(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.Data["wechatQrTicket"] = wechatQrTicket
|
||||||
|
ctx.Data["wechatQrCodeUrl"] = wechatQrCodeUrl
|
||||||
|
ctx.Data["wechatQrExpireSeconds"] = setting.Wechat.OfficialAccount.TempQrExpireSeconds
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplSignInWechatQr)
|
ctx.HTML(http.StatusOK, tplSignInWechatQr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,6 +227,8 @@ func SignIn(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SignInNavbarSetting(ctx)
|
||||||
|
|
||||||
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
|
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("UserSignIn", err)
|
ctx.ServerError("UserSignIn", err)
|
||||||
@@ -222,8 +239,6 @@ func SignIn(ctx *context.Context) {
|
|||||||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login"
|
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login"
|
||||||
ctx.Data["PageIsSignIn"] = true
|
ctx.Data["PageIsSignIn"] = true
|
||||||
ctx.Data["PageIsLogin"] = true
|
ctx.Data["PageIsLogin"] = true
|
||||||
ctx.Data["PageIsPasswordLogin"] = true
|
|
||||||
ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx)
|
|
||||||
|
|
||||||
if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
|
if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
|
||||||
context.SetCaptchaData(ctx)
|
context.SetCaptchaData(ctx)
|
||||||
@@ -478,9 +493,10 @@ func SignOut(ctx *context.Context) {
|
|||||||
|
|
||||||
// SignUp render the register page
|
// SignUp render the register page
|
||||||
func SignUp(ctx *context.Context) {
|
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"
|
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"
|
||||||
|
SignInNavbarSetting(ctx)
|
||||||
|
|
||||||
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
|
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ func SignInOpenID(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SignInNavbarSetting(ctx)
|
||||||
|
|
||||||
ctx.Data["PageIsSignIn"] = true
|
ctx.Data["PageIsSignIn"] = true
|
||||||
ctx.Data["PageIsLoginOpenID"] = true
|
ctx.Data["PageIsLoginOpenID"] = true
|
||||||
ctx.HTML(http.StatusOK, tplSignInOpenID)
|
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/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
web_auth_utils "code.gitea.io/gitea/routers/web/auth"
|
|
||||||
"code.gitea.io/gitea/services/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/db"
|
||||||
"code.gitea.io/gitea/services/auth/source/smtp"
|
"code.gitea.io/gitea/services/auth/source/smtp"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
@@ -42,11 +42,15 @@ func Account(ctx *context.Context) {
|
|||||||
loadAccountData(ctx)
|
loadAccountData(ctx)
|
||||||
|
|
||||||
// 界面原型:更新微信,展示公众号带参数二维码
|
// 界面原型:更新微信,展示公众号带参数二维码
|
||||||
_, err := web_auth_utils.GenerateWechatQr(ctx)
|
wechatQrTicket, wechatQrCodeUrl, err := auth_service.GetWechatQRTicket(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("微信创建二维码失败,跳过")
|
log.Warn("微信创建二维码失败,跳过")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.Data["wechatQrTicket"] = wechatQrTicket
|
||||||
|
ctx.Data["wechatQrCodeUrl"] = wechatQrCodeUrl
|
||||||
|
ctx.Data["wechatQrExpireSeconds"] = setting.Wechat.OfficialAccount.TempQrExpireSeconds
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplSettingsAccount)
|
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.MergeFrom(middleware.CommonTemplateContextData())
|
||||||
ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this
|
ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this
|
||||||
ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
|
ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
|
||||||
|
ctx.Data["EnableWechatQrLogin"] = setting.Wechat.OfficialAccount.Enabled
|
||||||
ctx.Data["Link"] = ctx.Link
|
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
|
// 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
|
EnableFederatedAvatar bool
|
||||||
EnableOpenIDSignIn bool
|
EnableOpenIDSignIn bool
|
||||||
EnableOpenIDSignUp bool
|
EnableOpenIDSignUp bool
|
||||||
|
EnableWechatQRSignIn bool
|
||||||
DisableRegistration bool
|
DisableRegistration bool
|
||||||
AllowOnlyExternalRegistration bool
|
AllowOnlyExternalRegistration bool
|
||||||
EnableCaptcha 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)
|
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)
|
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)
|
initRepoCommit(ctx, tmpDir, repo, repo.Owner, repo.DefaultBranch, commitMessage)
|
||||||
|
|
||||||
if err := repo_module.UpdateRepoSize(ctx, repo); err != nil {
|
if err := repo_module.UpdateRepoSize(ctx, repo); err != nil {
|
||||||
|
|||||||
@@ -177,7 +177,7 @@
|
|||||||
{{svg "octicon-person"}} {{ctx.Locale.Tr "register"}}
|
{{svg "octicon-person"}} {{ctx.Locale.Tr "register"}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{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"}}
|
{{svg "octicon-sign-in"}} {{ctx.Locale.Tr "sign_in"}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -221,12 +221,6 @@
|
|||||||
<input name="enable_federated_avatar" type="checkbox" {{if .enable_federated_avatar}}checked{{end}}>
|
<input name="enable_federated_avatar" type="checkbox" {{if .enable_federated_avatar}}checked{{end}}>
|
||||||
</div>
|
</div>
|
||||||
</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="inline field">
|
||||||
<div class="ui checkbox" id="disable-registration">
|
<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>
|
<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}}>
|
<input name="enable_open_id_sign_up" type="checkbox" {{if .enable_open_id_sign_up}}checked{{end}}>
|
||||||
</div>
|
</div>
|
||||||
</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="inline field">
|
||||||
<div class="ui checkbox" id="enable-captcha">
|
<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>
|
<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">
|
<overflow-menu class="ui secondary pointing tabular top attached borderless menu navbar secondary-nav">
|
||||||
<div class="overflow-menu-items tw-justify-center">
|
<div class="overflow-menu-items tw-justify-center">
|
||||||
|
<a class="{{if .PageIsLogin}}active {{end}}item" rel="nofollow" href="{{AppSubUrl}}/user/login">
|
||||||
{{/* 微信扫码登录 */}}
|
{{ctx.Locale.Tr "auth.login_userpass"}}
|
||||||
{{if .PageIsLogin}}
|
</a>
|
||||||
<a class="{{if .PageIsWechatQrLogin}} active {{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/login/wechat/official-account">
|
<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"}}
|
{{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>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{if .EnableSSPI}}
|
||||||
{{/* 2. 密码登录 */}}
|
<a class="item" rel="nofollow" href="{{AppSubUrl}}/user/login?auth_with_sspi=1">
|
||||||
{{if .PageIsLogin}}
|
{{svg "fontawesome-windows"}}
|
||||||
<a class="{{if .PageIsPasswordLogin}} active {{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/login">
|
SSPI
|
||||||
{{ctx.Locale.Tr "password"}}
|
|
||||||
</a>
|
</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}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</overflow-menu>
|
</overflow-menu>
|
||||||
|
{{end}}
|
||||||
|
|||||||
Reference in New Issue
Block a user