refactor: use POST for account binding endpoints and normalize reset responses
- Switch /api/oauth/email/bind and /api/oauth/wechat/bind from GET to POST with JSON body for better REST semantics - Normalize password reset endpoint to return consistent responses - Apply url.QueryEscape to WeChat code parameter for robustness
This commit is contained in:
+14
-21
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/QuantumNous/new-api/common"
|
"github.com/QuantumNous/new-api/common"
|
||||||
"github.com/QuantumNous/new-api/constant"
|
"github.com/QuantumNous/new-api/constant"
|
||||||
|
"github.com/QuantumNous/new-api/logger"
|
||||||
"github.com/QuantumNous/new-api/middleware"
|
"github.com/QuantumNous/new-api/middleware"
|
||||||
"github.com/QuantumNous/new-api/model"
|
"github.com/QuantumNous/new-api/model"
|
||||||
"github.com/QuantumNous/new-api/oauth"
|
"github.com/QuantumNous/new-api/oauth"
|
||||||
@@ -116,7 +117,6 @@ func GetStatus(c *gin.Context) {
|
|||||||
"user_agreement_enabled": legalSetting.UserAgreement != "",
|
"user_agreement_enabled": legalSetting.UserAgreement != "",
|
||||||
"privacy_policy_enabled": legalSetting.PrivacyPolicy != "",
|
"privacy_policy_enabled": legalSetting.PrivacyPolicy != "",
|
||||||
"checkin_enabled": operation_setting.GetCheckinSetting().Enabled,
|
"checkin_enabled": operation_setting.GetCheckinSetting().Enabled,
|
||||||
"_qn": "new-api",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据启用状态注入可选内容
|
// 根据启用状态注入可选内容
|
||||||
@@ -308,31 +308,24 @@ func SendPasswordResetEmail(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !model.IsEmailAlreadyTaken(email) {
|
if model.IsEmailAlreadyTaken(email) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
code := common.GenerateVerificationCode(0)
|
||||||
"success": false,
|
common.RegisterVerificationCodeWithKey(email, code, common.PasswordResetPurpose)
|
||||||
"message": "该邮箱地址未注册",
|
link := fmt.Sprintf("%s/user/reset?email=%s&token=%s", system_setting.ServerAddress, email, code)
|
||||||
})
|
subject := fmt.Sprintf("%s密码重置", common.SystemName)
|
||||||
return
|
content := fmt.Sprintf("<p>您好,你正在进行%s密码重置。</p>"+
|
||||||
}
|
"<p>点击 <a href='%s'>此处</a> 进行密码重置。</p>"+
|
||||||
code := common.GenerateVerificationCode(0)
|
"<p>如果链接无法点击,请尝试点击下面的链接或将其复制到浏览器中打开:<br> %s </p>"+
|
||||||
common.RegisterVerificationCodeWithKey(email, code, common.PasswordResetPurpose)
|
"<p>重置链接 %d 分钟内有效,如果不是本人操作,请忽略。</p>", common.SystemName, link, link, common.VerificationValidMinutes)
|
||||||
link := fmt.Sprintf("%s/user/reset?email=%s&token=%s", system_setting.ServerAddress, email, code)
|
err := common.SendEmail(subject, email, content)
|
||||||
subject := fmt.Sprintf("%s密码重置", common.SystemName)
|
if err != nil {
|
||||||
content := fmt.Sprintf("<p>您好,你正在进行%s密码重置。</p>"+
|
logger.LogError(c.Request.Context(), fmt.Sprintf("failed to send password reset email to %s: %s", email, err.Error()))
|
||||||
"<p>点击 <a href='%s'>此处</a> 进行密码重置。</p>"+
|
}
|
||||||
"<p>如果链接无法点击,请尝试点击下面的链接或将其复制到浏览器中打开:<br> %s </p>"+
|
|
||||||
"<p>重置链接 %d 分钟内有效,如果不是本人操作,请忽略。</p>", common.SystemName, link, link, common.VerificationValidMinutes)
|
|
||||||
err := common.SendEmail(subject, email, content)
|
|
||||||
if err != nil {
|
|
||||||
common.ApiError(c, err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "",
|
"message": "",
|
||||||
})
|
})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PasswordResetRequest struct {
|
type PasswordResetRequest struct {
|
||||||
|
|||||||
+12
-2
@@ -925,9 +925,19 @@ func ManageUser(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type emailBindRequest struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
func EmailBind(c *gin.Context) {
|
func EmailBind(c *gin.Context) {
|
||||||
email := c.Query("email")
|
var req emailBindRequest
|
||||||
code := c.Query("code")
|
if err := common.DecodeJson(c.Request.Body, &req); err != nil {
|
||||||
|
common.ApiError(c, errors.New("invalid request body"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
email := req.Email
|
||||||
|
code := req.Code
|
||||||
if !common.VerifyCodeWithKey(email, code, common.EmailVerificationPurpose) {
|
if !common.VerifyCodeWithKey(email, code, common.EmailVerificationPurpose) {
|
||||||
common.ApiErrorI18n(c, i18n.MsgUserVerificationCodeError)
|
common.ApiErrorI18n(c, i18n.MsgUserVerificationCodeError)
|
||||||
return
|
return
|
||||||
|
|||||||
+15
-2
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ func getWeChatIdByCode(code string) (string, error) {
|
|||||||
if code == "" {
|
if code == "" {
|
||||||
return "", errors.New("无效的参数")
|
return "", errors.New("无效的参数")
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/wechat/user?code=%s", common.WeChatServerAddress, code), nil)
|
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/wechat/user?code=%s", common.WeChatServerAddress, url.QueryEscape(code)), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -121,6 +122,10 @@ func WeChatAuth(c *gin.Context) {
|
|||||||
setupLogin(&user, c)
|
setupLogin(&user, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type wechatBindRequest struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
func WeChatBind(c *gin.Context) {
|
func WeChatBind(c *gin.Context) {
|
||||||
if !common.WeChatAuthEnabled {
|
if !common.WeChatAuthEnabled {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
@@ -129,7 +134,15 @@ func WeChatBind(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
code := c.Query("code")
|
var req wechatBindRequest
|
||||||
|
if err := common.DecodeJson(c.Request.Body, &req); err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "无效的请求",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
code := req.Code
|
||||||
wechatId, err := getWeChatIdByCode(code)
|
wechatId, err := getWeChatIdByCode(code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)
|
apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)
|
||||||
// OAuth routes - specific routes must come before :provider wildcard
|
// OAuth routes - specific routes must come before :provider wildcard
|
||||||
apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), controller.GenerateOAuthCode)
|
apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), controller.GenerateOAuthCode)
|
||||||
apiRouter.GET("/oauth/email/bind", middleware.CriticalRateLimit(), controller.EmailBind)
|
apiRouter.POST("/oauth/email/bind", middleware.CriticalRateLimit(), controller.EmailBind)
|
||||||
// Non-standard OAuth (WeChat, Telegram) - keep original routes
|
// Non-standard OAuth (WeChat, Telegram) - keep original routes
|
||||||
apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth)
|
apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth)
|
||||||
apiRouter.GET("/oauth/wechat/bind", middleware.CriticalRateLimit(), controller.WeChatBind)
|
apiRouter.POST("/oauth/wechat/bind", middleware.CriticalRateLimit(), controller.WeChatBind)
|
||||||
apiRouter.GET("/oauth/telegram/login", middleware.CriticalRateLimit(), controller.TelegramLogin)
|
apiRouter.GET("/oauth/telegram/login", middleware.CriticalRateLimit(), controller.TelegramLogin)
|
||||||
apiRouter.GET("/oauth/telegram/bind", middleware.CriticalRateLimit(), controller.TelegramBind)
|
apiRouter.GET("/oauth/telegram/bind", middleware.CriticalRateLimit(), controller.TelegramBind)
|
||||||
// Standard OAuth providers (GitHub, Discord, OIDC, LinuxDO) - unified route
|
// Standard OAuth providers (GitHub, Discord, OIDC, LinuxDO) - unified route
|
||||||
|
|||||||
@@ -306,9 +306,9 @@ const PersonalSetting = () => {
|
|||||||
|
|
||||||
const bindWeChat = async () => {
|
const bindWeChat = async () => {
|
||||||
if (inputs.wechat_verification_code === '') return;
|
if (inputs.wechat_verification_code === '') return;
|
||||||
const res = await API.get(
|
const res = await API.post('/api/oauth/wechat/bind', {
|
||||||
`/api/oauth/wechat/bind?code=${inputs.wechat_verification_code}`,
|
code: inputs.wechat_verification_code,
|
||||||
);
|
});
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
showSuccess(t('微信账户绑定成功!'));
|
showSuccess(t('微信账户绑定成功!'));
|
||||||
@@ -378,9 +378,10 @@ const PersonalSetting = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await API.get(
|
const res = await API.post('/api/oauth/email/bind', {
|
||||||
`/api/oauth/email/bind?email=${inputs.email}&code=${inputs.email_verification_code}`,
|
email: inputs.email,
|
||||||
);
|
code: inputs.email_verification_code,
|
||||||
|
});
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
showSuccess(t('邮箱账户绑定成功!'));
|
showSuccess(t('邮箱账户绑定成功!'));
|
||||||
|
|||||||
Reference in New Issue
Block a user