This commit is contained in:
ryan
2026-06-08 20:08:14 +08:00
parent 02f458856d
commit cd3d0c9f82
61 changed files with 2180 additions and 1891 deletions
+9 -9
View File
@@ -105,10 +105,10 @@ func jwtSign(payload []byte, secret []byte) string {
func jwtVerify(token string, secret []byte) ([]byte, error) {
parts := strings.Split(token, ".")
if len(parts) != 3 {
return nil, errors.New("invalid token format")
return nil, errors.New(errInvalidTokenFormat)
}
if parts[0] != jwtHeaderB64 {
return nil, errors.New("invalid header")
return nil, errors.New(errInvalidHeader)
}
sigInput := parts[0] + "." + parts[1]
@@ -122,7 +122,7 @@ func jwtVerify(token string, secret []byte) ([]byte, error) {
}
if !hmac.Equal(expectedSig, actualSig) {
return nil, errors.New("signature mismatch")
return nil, errors.New(errSignatureMismatch)
}
payload, err := b64urlDecode(parts[1])
@@ -195,25 +195,25 @@ func GenerateChallenge(secret []byte, conf ChallengeConfig, scope string) (*Chal
func VerifyChallengeSolutions(token string, solutions []int, secret []byte, expectedScope string) (*ChallengePayload, error) {
payloadBytes, err := jwtVerify(token, secret)
if err != nil {
return nil, errors.New("invalid_token")
return nil, errors.New(errInvalidToken)
}
var payload ChallengePayload
if err := json.Unmarshal(payloadBytes, &payload); err != nil {
return nil, errors.New("invalid_token")
return nil, errors.New(errInvalidToken)
}
if expectedScope != "" && payload.Scope != expectedScope {
return nil, errors.New("scope_mismatch")
return nil, errors.New(errScopeMismatch)
}
now := time.Now().UnixNano() / int64(time.Millisecond)
if payload.Expires < now {
return nil, errors.New("expired")
return nil, errors.New(errExpired)
}
if len(solutions) != payload.Count {
return nil, errors.New("invalid_solutions")
return nil, errors.New(errInvalidSolutions)
}
tokenFnv := fnv1a(token)
@@ -229,7 +229,7 @@ func VerifyChallengeSolutions(token string, solutions []int, secret []byte, expe
hashHex := hex.EncodeToString(hashBytes[:])
if !strings.HasPrefix(hashHex, target) {
return nil, errors.New("invalid_solution")
return nil, errors.New(errInvalidSolution)
}
}
+28
View File
@@ -0,0 +1,28 @@
/*
Copyright 2026 Arctel.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cap
const (
errInvalidTokenFormat = "invalid token format"
errInvalidHeader = "invalid header"
errSignatureMismatch = "signature mismatch"
errInvalidToken = "invalid_token"
errScopeMismatch = "scope_mismatch"
errExpired = "expired"
errInvalidSolutions = "invalid_solutions"
errInvalidSolution = "invalid_solution"
)
+12 -12
View File
@@ -54,28 +54,28 @@ func encryptBytes(signKey string, plaintext []byte) (string, error) {
// 将 hex 编码的密钥转换为字节
key, err := hex.DecodeString(signKey)
if err != nil {
return "", fmt.Errorf("invalid sign key: %w", err)
return "", fmt.Errorf(errInvalidSignKey, err)
}
if len(key) != 32 {
return "", errors.New("sign key must be 32 bytes (64 hex characters)")
return "", errors.New(errSignKeyLengthInvalid)
}
// 创建 AES cipher
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("failed to create cipher: %w", err)
return "", fmt.Errorf(errCreateCipherFailed, err)
}
// 使用 GCM 模式(Galois/Counter Mode)
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", fmt.Errorf("failed to create GCM: %w", err)
return "", fmt.Errorf(errCreateGCMFailed, err)
}
// 生成随机 nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", fmt.Errorf("failed to generate nonce: %w", err)
return "", fmt.Errorf(errGenerateNonceFailed, err)
}
// 加密数据
@@ -90,34 +90,34 @@ func decryptBytes(signKey string, ciphertext string) ([]byte, error) {
// 将 hex 编码的密钥转换为字节
key, err := hex.DecodeString(signKey)
if err != nil {
return nil, fmt.Errorf("invalid sign key: %w", err)
return nil, fmt.Errorf(errInvalidSignKey, err)
}
if len(key) != 32 {
return nil, errors.New("sign key must be 32 bytes (64 hex characters)")
return nil, errors.New(errSignKeyLengthInvalid)
}
// 解码 base64 密文
data, err := Base64Decode(ciphertext)
if err != nil {
return nil, fmt.Errorf("failed to decode ciphertext: %w", err)
return nil, fmt.Errorf(errDecodeCiphertextFailed, err)
}
// 创建 AES cipher
block, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("failed to create cipher: %w", err)
return nil, fmt.Errorf(errCreateCipherFailed, err)
}
// 使用 GCM 模式
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf("failed to create GCM: %w", err)
return nil, fmt.Errorf(errCreateGCMFailed, err)
}
// 提取 nonce
nonceSize := gcm.NonceSize()
if len(data) < nonceSize {
return nil, errors.New("ciphertext too short")
return nil, errors.New(errCiphertextTooShort)
}
nonce, ciphertextBytes := data[:nonceSize], data[nonceSize:]
@@ -125,7 +125,7 @@ func decryptBytes(signKey string, ciphertext string) ([]byte, error) {
// 解密数据
plaintext, err := gcm.Open(nil, nonce, ciphertextBytes, nil)
if err != nil {
return nil, fmt.Errorf("failed to decrypt: %w", err)
return nil, fmt.Errorf(errDecryptFailed, err)
}
return plaintext, nil
+1 -1
View File
@@ -29,7 +29,7 @@ type StringArray []string
func (sa *StringArray) Scan(value interface{}) error {
bytesValue, ok := value.([]byte)
if !ok {
return fmt.Errorf("invalid value: %v", value)
return fmt.Errorf(errInvalidCustomValue, value)
}
return json.Unmarshal(bytesValue, sa)
}
+31
View File
@@ -0,0 +1,31 @@
/*
Copyright 2026 Arctel.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
const (
errCreateHTTPRequestFailed = "创建HTTP请求失败: %w"
errHTTPRequestFailed = "请求%s接口失败: %w"
errInvalidCustomValue = "invalid value: %v"
errInvalidSignKey = "invalid sign key: %w"
errSignKeyLengthInvalid = "sign key must be 32 bytes (64 hex characters)"
errCreateCipherFailed = "failed to create cipher: %w"
errCreateGCMFailed = "failed to create GCM: %w"
errGenerateNonceFailed = "failed to generate nonce: %w"
errDecodeCiphertextFailed = "failed to decode ciphertext: %w"
errCiphertextTooShort = "ciphertext too short"
errDecryptFailed = "failed to decrypt: %w"
)
+2 -2
View File
@@ -55,7 +55,7 @@ func SetHTTPClient(c *http.Client) {
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 {
return nil, fmt.Errorf("创建HTTP请求失败: %w", err)
return nil, fmt.Errorf(errCreateHTTPRequestFailed, err)
}
if cookies != nil {
@@ -72,7 +72,7 @@ func Request(ctx context.Context, method, url string, body io.Reader, headers, c
resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("请求%s接口失败: %w", url, err)
return nil, fmt.Errorf(errHTTPRequestFailed, url, err)
}
return resp, nil
+28
View File
@@ -0,0 +1,28 @@
/*
Copyright 2026 Arctel.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mail
const (
errDialTLSFailed = "dial tls failed: %w"
errSMTPClientCreationFailed = "smtp client creation failed: %w"
errSMTPAuthFailed = "smtp auth failed: %w"
errSMTPMailCommandFailed = "smtp mail command failed: %w"
errSMTPRcptCommandFailed = "smtp rcpt command failed: %w"
errSMTPDataCommandFailed = "smtp data command failed: %w"
errSMTPWritingBodyFailed = "smtp writing body failed: %w"
errSendMailFailed = "send mail failed: %w"
)
+8 -8
View File
@@ -69,38 +69,38 @@ func SendMailHTML(cfg Config, to string, subject, body string) error {
dialer := &net.Dialer{Timeout: 5 * time.Second}
conn, err := tls.DialWithDialer(dialer, "tcp", addr, tlsConfig)
if err != nil {
return fmt.Errorf("dial tls failed: %w", err)
return fmt.Errorf(errDialTLSFailed, err)
}
defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(10 * time.Second))
client, err := smtp.NewClient(conn, cfg.Host)
if err != nil {
return fmt.Errorf("smtp client creation failed: %w", err)
return fmt.Errorf(errSMTPClientCreationFailed, err)
}
defer client.Close()
if err = client.Auth(auth); err != nil {
return fmt.Errorf("smtp auth failed: %w", err)
return fmt.Errorf(errSMTPAuthFailed, err)
}
if err = client.Mail(cfg.Username); err != nil {
return fmt.Errorf("smtp mail command failed: %w", err)
return fmt.Errorf(errSMTPMailCommandFailed, err)
}
if err = client.Rcpt(to); err != nil {
return fmt.Errorf("smtp rcpt command failed: %w", err)
return fmt.Errorf(errSMTPRcptCommandFailed, err)
}
w, err := client.Data()
if err != nil {
return fmt.Errorf("smtp data command failed: %w", err)
return fmt.Errorf(errSMTPDataCommandFailed, err)
}
defer w.Close()
_, err = w.Write([]byte(message))
if err != nil {
return fmt.Errorf("smtp writing body failed: %w", err)
return fmt.Errorf(errSMTPWritingBodyFailed, err)
}
return nil
@@ -109,7 +109,7 @@ func SendMailHTML(cfg Config, to string, subject, body string) error {
// For standard port (587 / 25), use smtp.SendMail directly (handles STARTTLS automatically if server supports it)
err := smtp.SendMail(addr, auth, cfg.Username, []string{to}, []byte(message))
if err != nil {
return fmt.Errorf("send mail failed: %w", err)
return fmt.Errorf(errSendMailFailed, err)
}
return nil