mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 00:26:37 +08:00
refactor(auth): improve session security, CAPTCHA validation and code hygiene
- Integrate CapWidget with dual-scope capability on the frontend and protect registration/send-email-code endpoints on the backend. - Set session cookie SameSite mode to Lax. - Propagate request context through auth source database operations and optimize username uniqueness validation. - Standardize local error naming to camelCase and resolve references. - Fix linter rules, missing SheetContent closing tag, and unit tests.
This commit is contained in:
@@ -45,7 +45,7 @@ func isEmailRegisterVerificationEnabled(ctx context.Context) bool {
|
||||
}
|
||||
|
||||
func isSMTPConfigured(ctx context.Context) bool {
|
||||
var host, port, username string
|
||||
var host, port, username, password string
|
||||
|
||||
var scHost model.SystemConfig
|
||||
if err := scHost.GetByKey(ctx, model.ConfigKeySMTPHost); err == nil {
|
||||
@@ -59,8 +59,12 @@ func isSMTPConfigured(ctx context.Context) bool {
|
||||
if err := scUser.GetByKey(ctx, model.ConfigKeySMTPUsername); err == nil {
|
||||
username = scUser.Value
|
||||
}
|
||||
var scPass model.SystemConfig
|
||||
if err := scPass.GetByKey(ctx, model.ConfigKeySMTPPassword); err == nil {
|
||||
password = scPass.Value
|
||||
}
|
||||
|
||||
return host != "" && port != "" && username != ""
|
||||
return host != "" && port != "" && username != "" && password != ""
|
||||
}
|
||||
|
||||
func generateVerificationCode() (string, error) {
|
||||
@@ -241,8 +245,6 @@ func validateRegisterEmailVerification(ctx context.Context, req *registerRequest
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
type updateProfileRequest struct {
|
||||
Nickname string `json:"nickname"`
|
||||
Email string `json:"email"`
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/config"
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/Rain-kl/Wavelet/internal/db/idgen"
|
||||
"github.com/Rain-kl/Wavelet/internal/logger"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
"github.com/gin-contrib/sessions"
|
||||
@@ -124,10 +125,12 @@ func Login(c *gin.Context) {
|
||||
var user model.User
|
||||
ctx := c.Request.Context()
|
||||
if err := db.DB(ctx).Where("username = ? OR email = ?", req.Username, req.Username).First(&user).Error; err != nil {
|
||||
logger.WarnF(ctx, "[LoginAudit] failed login attempt (username not found) for input: %s, IP: %s", req.Username, c.ClientIP())
|
||||
c.JSON(http.StatusOK, util.Err(errUsernameOrPasswordWrong))
|
||||
return
|
||||
}
|
||||
if !user.IsActive {
|
||||
logger.WarnF(ctx, "[LoginAudit] banned user login attempt for username: %s, ID: %d, IP: %s", user.Username, user.ID, c.ClientIP())
|
||||
c.JSON(http.StatusOK, util.Err(common.BannedAccount))
|
||||
return
|
||||
}
|
||||
@@ -136,6 +139,7 @@ func Login(c *gin.Context) {
|
||||
isPlaintext := !user.IsPasswordEncrypted()
|
||||
|
||||
if !user.CheckPassword(req.Password) {
|
||||
logger.WarnF(ctx, "[LoginAudit] failed login attempt (incorrect password) for username: %s, ID: %d, IP: %s", user.Username, user.ID, c.ClientIP())
|
||||
c.JSON(http.StatusOK, util.Err(errUsernameOrPasswordWrong))
|
||||
return
|
||||
}
|
||||
@@ -165,6 +169,8 @@ func Login(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.InfoF(ctx, "[LoginAudit] successful login for user: %s, ID: %d, IP: %s", user.Username, user.ID, c.ClientIP())
|
||||
|
||||
c.JSON(http.StatusOK, util.OK(oauth.BuildBasicUserInfo(&user, needChangePassword)))
|
||||
}
|
||||
|
||||
@@ -264,6 +270,11 @@ func Register(c *gin.Context) {
|
||||
// @Router /api/v1/user/logout [get]
|
||||
func Logout(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
userID := session.Get(oauth.UserIDKey)
|
||||
username := session.Get(oauth.UserNameKey)
|
||||
if userID != nil {
|
||||
logger.InfoF(c.Request.Context(), "[LoginAudit] user logged out: %v, ID: %v, IP: %s", username, userID, c.ClientIP())
|
||||
}
|
||||
session.Options(util.GetSessionOptions(-1))
|
||||
session.Clear()
|
||||
if err := session.Save(); err != nil {
|
||||
|
||||
@@ -383,6 +383,9 @@ func TestLoginEmailVerificationFallbackForEmptyEmail(t *testing.T) {
|
||||
if err := dbConn.Model(&model.SystemConfig{}).Where("key = ?", model.ConfigKeySMTPUsername).Update("value", "smtpuser").Error; err != nil {
|
||||
t.Fatalf("set SMTP username failed: %v", err)
|
||||
}
|
||||
if err := dbConn.Model(&model.SystemConfig{}).Where("key = ?", model.ConfigKeySMTPPassword).Update("value", "smtppassword").Error; err != nil {
|
||||
t.Fatalf("set SMTP password failed: %v", err)
|
||||
}
|
||||
|
||||
// Invalidate the system config cache in Redis
|
||||
if err := db.Redis.Del(context.Background(), db.PrefixedKey(model.SystemConfigRedisHashKey)).Err(); err != nil {
|
||||
@@ -522,8 +525,8 @@ func TestAccessTokenEndpointsDisallowTokenAuth(t *testing.T) {
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("decode response failed: %v", err)
|
||||
}
|
||||
if resp.ErrorMsg != oauth.TokenAuthNotAllowed {
|
||||
t.Errorf("expected error message %q, got %q", oauth.TokenAuthNotAllowed, resp.ErrorMsg)
|
||||
if resp.ErrorMsg != oauth.ErrTokenAuthNotAllowed {
|
||||
t.Errorf("expected error message %q, got %q", oauth.ErrTokenAuthNotAllowed, resp.ErrorMsg)
|
||||
}
|
||||
|
||||
// 4. Test that accessing using a Session succeeds
|
||||
@@ -684,5 +687,3 @@ func TestChangePasswordRevocation(t *testing.T) {
|
||||
t.Errorf("expected access token to be revoked (401), got %d", wTokenAfter.Code)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user