mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-03 23:06:36 +08:00
refactor: extract magic numbers to named constants for mnd lint compliance
This commit is contained in:
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package cap 提供人机验证(CAPTCHA)功能
|
||||
package cap
|
||||
|
||||
import (
|
||||
@@ -29,7 +30,15 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const jwtHeaderB64 = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
|
||||
const (
|
||||
jwtHeaderB64 = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
|
||||
jwtPartsCount = 3 // JWT 三段结构
|
||||
defaultChallengeCount = 50 // 默认 PoW 难题数
|
||||
defaultChallengeSize = 32 // 默认盐值长度
|
||||
defaultDifficulty = 4 // 默认难度
|
||||
defaultNonceLength = 25 // 随机 Nonce 字节长度
|
||||
defaultExpires = 10 * time.Minute // 默认过期时间
|
||||
)
|
||||
|
||||
// ChallengeConfig holds parameters for the PoW challenge
|
||||
type ChallengeConfig struct {
|
||||
@@ -104,7 +113,7 @@ func jwtSign(payload []byte, secret []byte) string {
|
||||
|
||||
func jwtVerify(token string, secret []byte) ([]byte, error) {
|
||||
parts := strings.Split(token, ".")
|
||||
if len(parts) != 3 {
|
||||
if len(parts) != jwtPartsCount {
|
||||
return nil, errors.New(errInvalidTokenFormat)
|
||||
}
|
||||
if parts[0] != jwtHeaderB64 {
|
||||
@@ -135,7 +144,7 @@ func jwtVerify(token string, secret []byte) ([]byte, error) {
|
||||
|
||||
func jwtSigHex(token string) string {
|
||||
parts := strings.Split(token, ".")
|
||||
if len(parts) != 3 {
|
||||
if len(parts) != jwtPartsCount {
|
||||
return ""
|
||||
}
|
||||
sigBytes, err := b64urlDecode(parts[2])
|
||||
@@ -148,23 +157,23 @@ func jwtSigHex(token string) string {
|
||||
// GenerateChallenge produces a new challenge and signed token
|
||||
func GenerateChallenge(secret []byte, conf ChallengeConfig, scope string) (*ChallengeResponse, error) {
|
||||
if conf.Count <= 0 {
|
||||
conf.Count = 50
|
||||
conf.Count = defaultChallengeCount
|
||||
}
|
||||
if conf.Size <= 0 {
|
||||
conf.Size = 32
|
||||
conf.Size = defaultChallengeSize
|
||||
}
|
||||
if conf.Difficulty <= 0 {
|
||||
conf.Difficulty = 4
|
||||
conf.Difficulty = defaultDifficulty
|
||||
}
|
||||
if conf.Expires <= 0 {
|
||||
conf.Expires = 10 * time.Minute
|
||||
conf.Expires = defaultExpires
|
||||
}
|
||||
|
||||
now := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
expires := now + int64(conf.Expires/time.Millisecond)
|
||||
|
||||
payload := ChallengePayload{
|
||||
Nonce: randomHex(25),
|
||||
Nonce: randomHex(defaultNonceLength),
|
||||
Count: conf.Count,
|
||||
Size: conf.Size,
|
||||
Difficulty: conf.Difficulty,
|
||||
|
||||
@@ -30,6 +30,18 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
)
|
||||
|
||||
const (
|
||||
managerDefaultChallengeCount = 1
|
||||
managerDefaultChallengeSize = 32
|
||||
defaultChallengeDifficulty = 4
|
||||
defaultChallengeTTL = 10 * time.Minute
|
||||
defaultTokenTTL = 20 * time.Minute
|
||||
redeemTokenIDLength = 8 // 兑换 Token ID 字节长度
|
||||
redeemVerTokenLength = 15 // 兑换验证 Token 字节长度
|
||||
tokenPartsCount = 2 // 兑换 Token 由两部分组成
|
||||
valuePartsCount = 2 // 存储值由 scope 和过期时间组成
|
||||
)
|
||||
|
||||
// Config holds settings for the CAPTCHA manager
|
||||
type Config struct {
|
||||
Secret []byte // HMAC signing key
|
||||
@@ -49,19 +61,19 @@ type Manager struct {
|
||||
// NewManager creates a new CAPTCHA Manager
|
||||
func NewManager(conf Config, store Store) *Manager {
|
||||
if conf.ChallengeCount <= 0 {
|
||||
conf.ChallengeCount = 1
|
||||
conf.ChallengeCount = managerDefaultChallengeCount
|
||||
}
|
||||
if conf.ChallengeSize <= 0 {
|
||||
conf.ChallengeSize = 32
|
||||
conf.ChallengeSize = managerDefaultChallengeSize
|
||||
}
|
||||
if conf.ChallengeDifficulty <= 0 {
|
||||
conf.ChallengeDifficulty = 4
|
||||
conf.ChallengeDifficulty = defaultChallengeDifficulty
|
||||
}
|
||||
if conf.ChallengeTTL <= 0 {
|
||||
conf.ChallengeTTL = 10 * time.Minute
|
||||
conf.ChallengeTTL = defaultChallengeTTL
|
||||
}
|
||||
if conf.TokenTTL <= 0 {
|
||||
conf.TokenTTL = 20 * time.Minute
|
||||
conf.TokenTTL = defaultTokenTTL
|
||||
}
|
||||
return &Manager{
|
||||
conf: conf,
|
||||
@@ -116,8 +128,8 @@ func (m *Manager) Redeem(ctx context.Context, token string, solutions []int, sco
|
||||
}
|
||||
|
||||
// Generate a redeem token formatted as "id:verToken"
|
||||
id := randomHex(8)
|
||||
verToken := randomHex(15)
|
||||
id := randomHex(redeemTokenIDLength)
|
||||
verToken := randomHex(redeemVerTokenLength)
|
||||
verHashBytes := sha256.Sum256([]byte(verToken))
|
||||
verHashHex := hex.EncodeToString(verHashBytes[:])
|
||||
|
||||
@@ -147,7 +159,7 @@ func (m *Manager) VerifyToken(ctx context.Context, token string, expectedScope s
|
||||
return false, nil
|
||||
}
|
||||
parts := strings.Split(token, ":")
|
||||
if len(parts) != 2 {
|
||||
if len(parts) != tokenPartsCount {
|
||||
return false, nil
|
||||
}
|
||||
id := parts[0]
|
||||
@@ -169,7 +181,7 @@ func (m *Manager) VerifyToken(ctx context.Context, token string, expectedScope s
|
||||
}
|
||||
|
||||
valParts := strings.Split(val, "|")
|
||||
if len(valParts) != 2 {
|
||||
if len(valParts) != valuePartsCount {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -253,11 +265,11 @@ func GetDefaultManager() *Manager {
|
||||
secret = []byte("default-captcha-secret-key-at-least-16-bytes")
|
||||
}
|
||||
|
||||
challengeCount := 1
|
||||
challengeSize := 32
|
||||
challengeDifficulty := 4
|
||||
challengeTTL := 10 * time.Minute
|
||||
tokenTTL := 20 * time.Minute
|
||||
challengeCount := managerDefaultChallengeCount
|
||||
challengeSize := managerDefaultChallengeSize
|
||||
challengeDifficulty := defaultChallengeDifficulty
|
||||
challengeTTL := defaultChallengeTTL
|
||||
tokenTTL := defaultTokenTTL
|
||||
|
||||
var store Store
|
||||
if config.Config != nil && config.Config.Redis.Enabled && db.Redis != nil {
|
||||
|
||||
@@ -29,6 +29,9 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// aesKeyLength AES-256 密钥字节长度
|
||||
const aesKeyLength = 32
|
||||
|
||||
// Encrypt 使用 SignKey 加密字符串数据
|
||||
// signKey: 64 字符 hex 编码的密钥(对应 32 字节,用于 AES-256)
|
||||
// plaintext: 要加密的明文字符串
|
||||
@@ -56,7 +59,7 @@ func encryptBytes(signKey string, plaintext []byte) (string, error) {
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(errInvalidSignKey, err)
|
||||
}
|
||||
if len(key) != 32 {
|
||||
if len(key) != aesKeyLength {
|
||||
return "", errors.New(errSignKeyLengthInvalid)
|
||||
}
|
||||
|
||||
@@ -92,7 +95,7 @@ func decryptBytes(signKey string, ciphertext string) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errInvalidSignKey, err)
|
||||
}
|
||||
if len(key) != 32 {
|
||||
if len(key) != aesKeyLength {
|
||||
return nil, errors.New(errSignKeyLengthInvalid)
|
||||
}
|
||||
|
||||
|
||||
@@ -38,20 +38,30 @@ func IsLocalhost(urlStr string) bool {
|
||||
return hostname == "localhost" || hostname == "127.0.0.1" || hostname == "::1"
|
||||
}
|
||||
|
||||
// HTTP 客户端配置常量
|
||||
const (
|
||||
httpClientTimeout = 10 // HTTP 客户端超时时间(秒)
|
||||
httpMaxIdleConns = 100
|
||||
httpMaxIdleConnsPerHost = 20
|
||||
httpIdleConnTimeout = 60 // 空闲连接超时(秒)
|
||||
)
|
||||
|
||||
// 配置HTTP客户端 使用 otelhttp 自动注入 trace span
|
||||
var httpClient = &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
Timeout: httpClientTimeout * time.Second,
|
||||
Transport: otelhttp.NewTransport(&http.Transport{
|
||||
MaxIdleConns: 100,
|
||||
MaxIdleConnsPerHost: 20,
|
||||
IdleConnTimeout: 60 * time.Second,
|
||||
MaxIdleConns: httpMaxIdleConns,
|
||||
MaxIdleConnsPerHost: httpMaxIdleConnsPerHost,
|
||||
IdleConnTimeout: httpIdleConnTimeout * time.Second,
|
||||
}),
|
||||
}
|
||||
|
||||
// SetHTTPClient 替换全局 HTTP 客户端实例
|
||||
func SetHTTPClient(c *http.Client) {
|
||||
httpClient = c
|
||||
}
|
||||
|
||||
// Request 发送 HTTP 请求,支持自定义 Headers 和 Cookies
|
||||
func Request(ctx context.Context, method, url string, body io.Reader, headers, cookies map[string]string) (*http.Response, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, method, url, body)
|
||||
if err != nil {
|
||||
|
||||
+55
-47
@@ -27,6 +27,12 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
smtpSSLPort = 465 // SMTP SSL 端口
|
||||
smtpDialTimeout = 5 * time.Second // SMTP 连接超时
|
||||
smtpSessionDeadline = 10 * time.Second // SMTP 会话截止时间
|
||||
)
|
||||
|
||||
// Config represents SMTP mail configuration
|
||||
type Config struct {
|
||||
Host string
|
||||
@@ -61,49 +67,8 @@ func SendMailHTML(cfg Config, to string, subject, body string) error {
|
||||
auth := smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host)
|
||||
|
||||
// If using SSL port 465, we connection via TLS dial
|
||||
if cfg.Port == 465 {
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: cfg.Host,
|
||||
}
|
||||
dialer := &net.Dialer{Timeout: 5 * time.Second}
|
||||
conn, err := tls.DialWithDialer(dialer, "tcp", addr, tlsConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errDialTLSFailed, err)
|
||||
}
|
||||
defer func() { _ = conn.Close() }()
|
||||
_ = conn.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
|
||||
client, err := smtp.NewClient(conn, cfg.Host)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errSMTPClientCreationFailed, err)
|
||||
}
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
if err = client.Auth(auth); err != nil {
|
||||
return fmt.Errorf(errSMTPAuthFailed, err)
|
||||
}
|
||||
|
||||
if err = client.Mail(cfg.Username); err != nil {
|
||||
return fmt.Errorf(errSMTPMailCommandFailed, err)
|
||||
}
|
||||
|
||||
if err = client.Rcpt(to); err != nil {
|
||||
return fmt.Errorf(errSMTPRcptCommandFailed, err)
|
||||
}
|
||||
|
||||
w, err := client.Data()
|
||||
if err != nil {
|
||||
return fmt.Errorf(errSMTPDataCommandFailed, err)
|
||||
}
|
||||
defer func() { _ = w.Close() }()
|
||||
|
||||
_, err = w.Write([]byte(message))
|
||||
if err != nil {
|
||||
return fmt.Errorf(errSMTPWritingBodyFailed, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
if cfg.Port == smtpSSLPort {
|
||||
return sendMailViaSSL(addr, auth, cfg, to, message)
|
||||
}
|
||||
|
||||
// For standard port (587 / 25), use smtp.SendMail directly (handles STARTTLS automatically if server supports it)
|
||||
@@ -115,6 +80,49 @@ func SendMailHTML(cfg Config, to string, subject, body string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendMailViaSSL 通过 TLS 直接连接 SMTP SSL 端口发送邮件
|
||||
func sendMailViaSSL(addr string, auth smtp.Auth, cfg Config, to, message string) error {
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: cfg.Host,
|
||||
}
|
||||
dialer := &net.Dialer{Timeout: smtpDialTimeout}
|
||||
conn, err := tls.DialWithDialer(dialer, "tcp", addr, tlsConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errDialTLSFailed, err)
|
||||
}
|
||||
defer func() { _ = conn.Close() }()
|
||||
_ = conn.SetDeadline(time.Now().Add(smtpSessionDeadline))
|
||||
|
||||
client, err := smtp.NewClient(conn, cfg.Host)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errSMTPClientCreationFailed, err)
|
||||
}
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
if err = client.Auth(auth); err != nil {
|
||||
return fmt.Errorf(errSMTPAuthFailed, err)
|
||||
}
|
||||
if err = client.Mail(cfg.Username); err != nil {
|
||||
return fmt.Errorf(errSMTPMailCommandFailed, err)
|
||||
}
|
||||
if err = client.Rcpt(to); err != nil {
|
||||
return fmt.Errorf(errSMTPRcptCommandFailed, err)
|
||||
}
|
||||
|
||||
w, err := client.Data()
|
||||
if err != nil {
|
||||
return fmt.Errorf(errSMTPDataCommandFailed, err)
|
||||
}
|
||||
defer func() { _ = w.Close() }()
|
||||
|
||||
_, err = w.Write([]byte(message))
|
||||
if err != nil {
|
||||
return fmt.Errorf(errSMTPWritingBodyFailed, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendMailWithLog sends a test email and records a detailed SMTP connection log
|
||||
func SendMailWithLog(cfg Config, to string, subject, body string) (string, error) {
|
||||
var logBuf bytes.Buffer
|
||||
@@ -127,8 +135,8 @@ func SendMailWithLog(cfg Config, to string, subject, body string) (string, error
|
||||
|
||||
var conn net.Conn
|
||||
var err error
|
||||
dialer := &net.Dialer{Timeout: 5 * time.Second}
|
||||
if cfg.Port == 465 {
|
||||
dialer := &net.Dialer{Timeout: smtpDialTimeout}
|
||||
if cfg.Port == smtpSSLPort {
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: cfg.Host,
|
||||
@@ -145,7 +153,7 @@ func SendMailWithLog(cfg Config, to string, subject, body string) (string, error
|
||||
logLine("System", "Connected successfully.")
|
||||
|
||||
// Set a 10-second session deadline for read/write operations
|
||||
_ = conn.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
_ = conn.SetDeadline(time.Now().Add(smtpSessionDeadline))
|
||||
|
||||
client, err := smtp.NewClient(conn, cfg.Host)
|
||||
if err != nil {
|
||||
@@ -155,7 +163,7 @@ func SendMailWithLog(cfg Config, to string, subject, body string) (string, error
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
// If not 465, support STARTTLS if available
|
||||
if cfg.Port != 465 {
|
||||
if cfg.Port != smtpSSLPort {
|
||||
if ok, _ := client.Extension("STARTTLS"); ok {
|
||||
logLine("C", "STARTTLS")
|
||||
tlsConfig := &tls.Config{
|
||||
|
||||
@@ -19,6 +19,12 @@ package util
|
||||
|
||||
import "strings"
|
||||
|
||||
// emailPartsCount 邮箱地址由 @ 分割为两部分
|
||||
const (
|
||||
emailPartsCount = 2
|
||||
emailLocalMinChars = 2 // 邮箱 local 部分掩码显示的最小字符数
|
||||
)
|
||||
|
||||
// DerefString 安全地解引用字符串指针,nil 返回空字符串
|
||||
func DerefString(s *string) string {
|
||||
if s == nil {
|
||||
@@ -30,12 +36,12 @@ func DerefString(s *string) string {
|
||||
// MaskEmail 安全脱敏用户的邮箱地址(例如 us***@example.com)
|
||||
func MaskEmail(email string) string {
|
||||
parts := strings.Split(email, "@")
|
||||
if len(parts) != 2 {
|
||||
if len(parts) != emailPartsCount {
|
||||
return email
|
||||
}
|
||||
local := parts[0]
|
||||
domain := parts[1]
|
||||
if len(local) <= 2 {
|
||||
if len(local) <= emailLocalMinChars {
|
||||
return "**@" + domain
|
||||
}
|
||||
return local[:2] + "***" + local[len(local)-1:] + "@" + domain
|
||||
|
||||
@@ -26,9 +26,12 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// uniqueIDBytes 生成唯一 ID 所需的随机字节长度
|
||||
const uniqueIDBytes = 32
|
||||
|
||||
// GenerateUniqueIDSimple 生成 64 位唯一标识符
|
||||
func GenerateUniqueIDSimple() string {
|
||||
randomBytes := make([]byte, 32)
|
||||
randomBytes := make([]byte, uniqueIDBytes)
|
||||
if _, err := io.ReadFull(rand.Reader, randomBytes); err != nil {
|
||||
// 如果随机数生成失败,使用 UUID 作为后备
|
||||
uuidBytes := []byte(uuid.NewString())
|
||||
|
||||
Reference in New Issue
Block a user