Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions backend/biz/team/usecase/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"github.com/chaitin/MonkeyCode/backend/db"
"github.com/chaitin/MonkeyCode/backend/domain"
"github.com/chaitin/MonkeyCode/backend/errcode"
"github.com/chaitin/MonkeyCode/backend/pkg/crypto"
"github.com/chaitin/MonkeyCode/backend/pkg/cvt"
)

Expand Down Expand Up @@ -269,12 +268,10 @@ func (u *TeamGroupUserUsecase) UpdateUser(ctx context.Context, req *domain.Updat
}

// generateResetPWDToken 生成重置密码的 token
// 使用 UUID 作为随机 handle,实际过期时间由 Redis TTL 控制,
// 避免 base32 填充字符在邮件传输中被破坏。
func (u *TeamGroupUserUsecase) generateResetPWDToken(ctx context.Context, userID uuid.UUID) (string, error) {
token, err := crypto.Simple(userID.String(), time.Now().Add(time.Hour*24*3))
if err != nil {
return "", err
}
return token, nil
return uuid.NewString(), nil
}

// sendResetPasswordEmail 发送重置密码邮件
Expand Down
46 changes: 13 additions & 33 deletions backend/biz/user/usecase/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"github.com/chaitin/MonkeyCode/backend/db"
"github.com/chaitin/MonkeyCode/backend/domain"
"github.com/chaitin/MonkeyCode/backend/errcode"
"github.com/chaitin/MonkeyCode/backend/pkg/crypto"
"github.com/chaitin/MonkeyCode/backend/pkg/cvt"
)

Expand Down Expand Up @@ -159,16 +158,12 @@ func (u *UserUsecase) SendBindEmailVerification(ctx context.Context, userID uuid
return errcode.ErrEmailTaken
}

// 生成验证 token
token, err := crypto.Simple(userID.String(), time.Now().Add(time.Hour*24))
if err != nil {
u.logger.ErrorContext(ctx, "generate bind email token failed", "error", err)
return errcode.ErrInternalServer.Wrap(err)
}
// 生成验证 token(使用 UUID,避免 base32 填充字符在邮件传输中被破坏)
token := uuid.NewString()

// 存储 token 到 Redis,格式:{token}:{email},有效期 24 小时
key := fmt.Sprintf("bind_email_token:%s", userID.String())
value := fmt.Sprintf("%s:%s", token, req.Email)
// 存储 token 到 Redis,key: bind_email_token:{token},value: {userID}:{email},有效期 24 小时
key := fmt.Sprintf("bind_email_token:%s", token)
value := fmt.Sprintf("%s:%s", userID.String(), req.Email)
if err := u.redis.Set(ctx, key, value, time.Hour*24).Err(); err != nil {
u.logger.ErrorContext(ctx, "set redis key failed", "error", err)
return errcode.ErrDatabaseOperation.Wrap(err)
Expand All @@ -194,21 +189,8 @@ func (u *UserUsecase) SendBindEmailVerification(ctx context.Context, userID uuid

// VerifyBindEmail 验证邮箱绑定
func (u *UserUsecase) VerifyBindEmail(ctx context.Context, token string) error {
// 验证 token 的有效性(检查签名和过期时间)
userIDStr, err := crypto.ValidateSimple(token)
if err != nil {
u.logger.WarnContext(ctx, "validate token failed", "error", err)
return errcode.ErrEmailVerifyFailed.Wrap(err)
}

userID, err := uuid.Parse(userIDStr)
if err != nil {
u.logger.WarnContext(ctx, "parse user id from token failed", "error", err)
return errcode.ErrEmailVerifyFailed.Wrap(err)
}

// 从 Redis 中取出存储的 token 和邮箱(一次性消费)
key := fmt.Sprintf("bind_email_token:%s", userID.String())
// 以 token 为 key 从 Redis 中取出 userID 和邮箱(一次性消费)
key := fmt.Sprintf("bind_email_token:%s", token)
redisValue, err := u.redis.GetDel(ctx, key).Result()
if err != nil {
if err == redis.Nil {
Expand All @@ -218,21 +200,19 @@ func (u *UserUsecase) VerifyBindEmail(ctx context.Context, token string) error {
return errcode.ErrDatabaseOperation.Wrap(err)
}

// 解析 Redis 中的值:{token}:{email}
// 解析 Redis 中的值:{userID}:{email}
parts := strings.SplitN(redisValue, ":", 2)
if len(parts) != 2 {
u.logger.WarnContext(ctx, "invalid redis value format", "value", redisValue)
return errcode.ErrEmailVerifyFailed
}

storedToken := parts[0]
email := parts[1]

// 验证 token 是否匹配(防止 token 替换)
if storedToken != token {
u.logger.WarnContext(ctx, "token mismatch")
return errcode.ErrEmailVerifyFailed
userID, err := uuid.Parse(parts[0])
if err != nil {
u.logger.WarnContext(ctx, "parse user id from redis value failed", "error", err)
return errcode.ErrEmailVerifyFailed.Wrap(err)
}
email := parts[1]

// 再次检查邮箱是否被其他用户占用(防止竞态条件)
existingUsers, err := u.repo.GetUserByEmail(ctx, []string{email})
Expand Down