- Add POST /user/email/code endpoint to send 6-digit verification code - Require email code verification before completing registration - Add email code cache with 10min TTL and 5/day send rate limit - Fix nil client guard, TLS conn leak, domain parsing, and Resend error body in email pkg - Deploy via ssh inline command using current branch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
117 lines
2.9 KiB
Go
117 lines
2.9 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
const (
|
|
UserSmsCodeTTL = 10 * time.Minute
|
|
UserSendSmsLimitTTL = 24 * time.Hour
|
|
UserSendSmsLimitCount = 5
|
|
)
|
|
|
|
const (
|
|
UserSmsCodePrefix = "user:sms_code:%s"
|
|
UserSendSmsLimit = "user:send_sms_limit:%s"
|
|
)
|
|
|
|
func GetUserSmsCode(ctx context.Context, phone string) (string, error) {
|
|
code, err := RedisClient.Get(ctx, fmt.Sprintf(UserSmsCodePrefix, phone)).Result()
|
|
if err != nil {
|
|
if err == redis.Nil {
|
|
return "", nil
|
|
}
|
|
return "", err
|
|
}
|
|
return code, nil
|
|
}
|
|
|
|
func SetUserSmsCode(ctx context.Context, phone, code string) error {
|
|
return RedisClient.Set(ctx, fmt.Sprintf(UserSmsCodePrefix, phone), code, UserSmsCodeTTL).Err()
|
|
}
|
|
|
|
func GetUserSendSmsLimit(ctx context.Context, phone string) (int, error) {
|
|
limit, err := RedisClient.Get(ctx, fmt.Sprintf(UserSendSmsLimit, phone)).Result()
|
|
if err != nil {
|
|
if err == redis.Nil {
|
|
return 0, nil
|
|
}
|
|
return 0, err
|
|
}
|
|
return strconv.Atoi(limit)
|
|
}
|
|
|
|
func SetUserSendSmsLimit(ctx context.Context, phone string) error {
|
|
count, err := RedisClient.Incr(ctx, fmt.Sprintf(UserSendSmsLimit, phone)).Result()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if count > UserSendSmsLimitCount {
|
|
return errors.New("send sms limit")
|
|
}
|
|
return RedisClient.Expire(ctx, fmt.Sprintf(UserSendSmsLimit, phone), UserSendSmsLimitTTL).Err()
|
|
}
|
|
|
|
func DeleteUserSmsCode(ctx context.Context, phone string) error {
|
|
return RedisClient.Del(ctx, fmt.Sprintf(UserSmsCodePrefix, phone)).Err()
|
|
}
|
|
|
|
const (
|
|
UserEmailCodeTTL = 10 * time.Minute
|
|
UserSendEmailLimitTTL = 24 * time.Hour
|
|
UserSendEmailLimitCount = 5
|
|
)
|
|
|
|
const (
|
|
UserEmailCodePrefix = "user:email_code:%s"
|
|
UserSendEmailLimit = "user:send_email_limit:%s"
|
|
)
|
|
|
|
func GetUserEmailCode(ctx context.Context, email string) (string, error) {
|
|
code, err := RedisClient.Get(ctx, fmt.Sprintf(UserEmailCodePrefix, email)).Result()
|
|
if err != nil {
|
|
if err == redis.Nil {
|
|
return "", nil
|
|
}
|
|
return "", err
|
|
}
|
|
return code, nil
|
|
}
|
|
|
|
func SetUserEmailCode(ctx context.Context, email, code string) error {
|
|
return RedisClient.Set(ctx, fmt.Sprintf(UserEmailCodePrefix, email), code, UserEmailCodeTTL).Err()
|
|
}
|
|
|
|
func DeleteUserEmailCode(ctx context.Context, email string) error {
|
|
return RedisClient.Del(ctx, fmt.Sprintf(UserEmailCodePrefix, email)).Err()
|
|
}
|
|
|
|
func GetUserSendEmailLimit(ctx context.Context, email string) (int, error) {
|
|
limit, err := RedisClient.Get(ctx, fmt.Sprintf(UserSendEmailLimit, email)).Result()
|
|
if err != nil {
|
|
if err == redis.Nil {
|
|
return 0, nil
|
|
}
|
|
return 0, err
|
|
}
|
|
return strconv.Atoi(limit)
|
|
}
|
|
|
|
func SetUserSendEmailLimit(ctx context.Context, email string) error {
|
|
key := fmt.Sprintf(UserSendEmailLimit, email)
|
|
count, err := RedisClient.Incr(ctx, key).Result()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if count > UserSendEmailLimitCount {
|
|
return errors.New("send email limit")
|
|
}
|
|
return RedisClient.Expire(ctx, key, UserSendEmailLimitTTL).Err()
|
|
}
|