hi-server/script/test_device_login.go
shanshanzhong bafeaa35cd
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 7m15s
feat(handler): 添加设备WebSocket端点及测试脚本
新增设备WebSocket通信端点/v1/app/ws/:userid/:device_number
添加测试脚本test_ws.go用于WebSocket连接测试
添加测试脚本test_device_login.go用于设备登录及绑定测试
2025-10-22 21:06:39 -07:00

240 lines
6.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"time"
)
func sha256Sum(b []byte) []byte {
sum := sha256.Sum256(b)
return sum[:]
}
func md5Hex(s string) string {
h := md5.Sum([]byte(s))
return hex.EncodeToString(h[:])
}
func deriveKey(secret string) []byte {
return sha256Sum([]byte(secret)) // 32 bytes for AES-256
}
func deriveIV(secret, nonce string) []byte {
ivFull := sha256Sum([]byte(md5Hex(nonce) + secret)) // 32 bytes
return ivFull[:aes.BlockSize] // 16 bytes IV
}
func pkcs7Pad(data []byte, blockSize int) []byte {
pad := blockSize - len(data)%blockSize
padding := bytes.Repeat([]byte{byte(pad)}, pad)
return append(data, padding...)
}
func pkcs7Unpad(data []byte) ([]byte, error) {
if len(data) == 0 {
return nil, errors.New("invalid data length")
}
pad := int(data[len(data)-1])
if pad <= 0 || pad > aes.BlockSize || pad > len(data) {
return nil, errors.New("invalid padding")
}
for i := 0; i < pad; i++ {
if data[len(data)-1-i] != byte(pad) {
return nil, errors.New("invalid padding content")
}
}
return data[:len(data)-pad], nil
}
func genNonce() string {
return fmt.Sprintf("%x", time.Now().UnixNano())
}
func encryptPayload(plain map[string]interface{}, secret string) ([]byte, string, error) {
nonce := genNonce()
key := deriveKey(secret)
iv := deriveIV(secret, nonce)
b, err := json.Marshal(plain)
if err != nil {
return nil, "", err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, "", err
}
mode := cipher.NewCBCEncrypter(block, iv)
padded := pkcs7Pad(b, aes.BlockSize)
cipherText := make([]byte, len(padded))
mode.CryptBlocks(cipherText, padded)
wrapper := map[string]string{
"data": base64.StdEncoding.EncodeToString(cipherText),
"time": nonce,
}
out, err := json.Marshal(wrapper)
return out, nonce, err
}
func decryptResponseBody(respBody []byte, secret string) (map[string]interface{}, error) {
var top map[string]interface{}
if err := json.Unmarshal(respBody, &top); err != nil {
return nil, err
}
// 响应格式可能是:
// { "code": 0, "msg": "ok", "data": { "data": "...", "time": "..." } }
// 或者直接就是 { "data": "...", "time": "..." }
var wrapper map[string]interface{}
if v, ok := top["data"].(map[string]interface{}); ok && v["data"] != nil && v["time"] != nil {
wrapper = v
} else {
wrapper = top
}
cipherB64, _ := wrapper["data"].(string)
nonce, _ := wrapper["time"].(string)
if cipherB64 == "" || nonce == "" {
return nil, errors.New("response missing data/time fields")
}
key := deriveKey(secret)
iv := deriveIV(secret, nonce)
cipherBytes, err := base64.StdEncoding.DecodeString(cipherB64)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
mode := cipher.NewCBCDecrypter(block, iv)
plainPadded := make([]byte, len(cipherBytes))
mode.CryptBlocks(plainPadded, cipherBytes)
plain, err := pkcs7Unpad(plainPadded)
if err != nil {
return nil, err
}
var out map[string]interface{}
if err := json.Unmarshal(plain, &out); err != nil {
return nil, err
}
return out, nil
}
func bindEmailWithPassword(serverURL, secret, token, email, password, userAgent string) error {
plain := map[string]interface{}{
"email": email,
"password": password,
}
body, _, err := encryptPayload(plain, secret)
if err != nil {
return err
}
req, err := http.NewRequest("POST", serverURL+"/v1/public/user/bind_email_with_password", bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", token)
req.Header.Set("Login-Type", "device")
req.Header.Set("User-Agent", userAgent)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
respBytes, _ := io.ReadAll(resp.Body)
fmt.Println("[绑定邮箱响应]", resp.StatusCode, string(respBytes))
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bind_email_with_password failed: %s", string(respBytes))
}
return nil
}
func main() {
secret := "c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx"
serverURL := "http://localhost:8080"
identifier := "AP4A.241205.014"
userAgent := "ppanel-go-test/1.0"
plain := map[string]interface{}{
"identifier": identifier,
"user_agent": userAgent,
}
body, _, err := encryptPayload(plain, secret)
if err != nil {
fmt.Println("加密失败:", err)
os.Exit(2)
}
req, err := http.NewRequest("POST", serverURL+"/v1/auth/login/device", bytes.NewReader(body))
if err != nil {
fmt.Println("请求创建失败:", err)
os.Exit(3)
}
req.Header.Set("Login-Type", "device")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", userAgent)
req.Header.Set("X-Original-Forwarded-For", "127.0.0.1")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("请求失败:", err)
os.Exit(4)
}
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("读取响应失败:", err)
os.Exit(5)
}
fmt.Println("[加密响应原文]", string(respBytes))
decrypted, err := decryptResponseBody(respBytes, secret)
if err != nil {
fmt.Println("解密失败:", err)
os.Exit(6)
}
fmt.Println("[解密响应明文]", decrypted)
var token string
if t, ok := decrypted["token"].(string); ok && t != "" {
token = t
fmt.Println("✅ 登录成功token =", token)
} else {
fmt.Println("⚠️ 未获取到 token")
}
// 新增:根据邮箱密码绑定设备号(需提供 EMAIL 和 PASSWORD 环境变量)
email := "client@qq.com"
password := "123456"
if token != "" && email != "" && password != "" {
if err := bindEmailWithPassword(serverURL, secret, token, email, password, userAgent); err != nil {
fmt.Println("绑定邮箱失败:", err)
} else {
fmt.Println("✅ 绑定邮箱成功")
}
} else {
fmt.Println("跳过绑定:缺少 token、EMAIL 或 PASSWORD")
}
}