多平台账号批量注册系统,支持 OpenAI / Grok / Kiro / Gemini 平台的全自动注册。 Features: - 用户名/密码注册登录(首个用户自动管理员) - JWT 认证 + bcrypt 密码加密 - 积分系统与多用户管理 - HuggingFace Space 弹性 Worker 节点管理 - Cloudflare Worker 负载均衡 - 实时日志推送(SSE/WebSocket) - 管理后台(用户管理、系统设置、代理池、卡池) Tech Stack: Go 1.25 / Gin / GORM / PostgreSQL / Vue 3 / Vite / TailwindCSS
151 lines
4.0 KiB
Go
151 lines
4.0 KiB
Go
package grpcweb
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
)
|
|
|
|
// DecodeSessionCookie 从 CreateUserAndSessionV2 的 gRPC-web 响应中提取 session_cookie
|
|
//
|
|
// 响应结构:
|
|
//
|
|
// gRPC-web frame: \x00 + 4字节长度 + payload
|
|
// CreateSessionV2Response {
|
|
// oneof response {
|
|
// CreateSessionResponse session = 1; ← 成功
|
|
// ExistingEmailSignInMethods existing = 2; ← 邮箱已存在
|
|
// }
|
|
// }
|
|
// CreateSessionResponse {
|
|
// Session session = 1;
|
|
// string session_cookie = 2; ← SSO token
|
|
// repeated string one_time_link_tokens = 3;
|
|
// }
|
|
func DecodeSessionCookie(body []byte) (string, error) {
|
|
if len(body) < 5 {
|
|
return "", fmt.Errorf("响应体过短: %d bytes", len(body))
|
|
}
|
|
|
|
// 跳过 gRPC-web 帧头(5 字节: flag + 4字节长度)
|
|
if body[0] != 0x00 {
|
|
return "", fmt.Errorf("非数据帧: flag=0x%02x", body[0])
|
|
}
|
|
msgLen := binary.BigEndian.Uint32(body[1:5])
|
|
if int(msgLen)+5 > len(body) {
|
|
return "", fmt.Errorf("帧长度不匹配: 声明 %d, 实际 %d", msgLen, len(body)-5)
|
|
}
|
|
msg := body[5 : 5+msgLen]
|
|
|
|
// 解析 CreateSessionV2Response
|
|
// field 1 (wire type 2) = CreateSessionResponse
|
|
sessionResponseData, err := extractBytesField(msg, 1)
|
|
if err != nil {
|
|
// 可能是 field 2 (ExistingEmailSignInMethods),邮箱已存在
|
|
if _, err2 := extractBytesField(msg, 2); err2 == nil {
|
|
return "", fmt.Errorf("邮箱已存在(需确认账户)")
|
|
}
|
|
return "", fmt.Errorf("解析 V2Response 失败: %w", err)
|
|
}
|
|
|
|
// 解析 CreateSessionResponse
|
|
// field 2 (wire type 2) = session_cookie (string)
|
|
cookie, err := extractStringField(sessionResponseData, 2)
|
|
if err != nil {
|
|
return "", fmt.Errorf("解析 session_cookie 失败: %w", err)
|
|
}
|
|
return cookie, nil
|
|
}
|
|
|
|
// DecodeSessionCookieV1 从 CreateUserAndSession (非V2) 的 gRPC-web 响应中提取 session_cookie
|
|
func DecodeSessionCookieV1(body []byte) (string, error) {
|
|
if len(body) < 5 {
|
|
return "", fmt.Errorf("响应体过短: %d bytes", len(body))
|
|
}
|
|
|
|
if body[0] != 0x00 {
|
|
return "", fmt.Errorf("非数据帧: flag=0x%02x", body[0])
|
|
}
|
|
msgLen := binary.BigEndian.Uint32(body[1:5])
|
|
if int(msgLen)+5 > len(body) {
|
|
return "", fmt.Errorf("帧长度不匹配")
|
|
}
|
|
msg := body[5 : 5+msgLen]
|
|
|
|
// CreateSessionResponse.session_cookie = field 2
|
|
cookie, err := extractStringField(msg, 2)
|
|
if err != nil {
|
|
return "", fmt.Errorf("解析 session_cookie 失败: %w", err)
|
|
}
|
|
return cookie, nil
|
|
}
|
|
|
|
// decodeVarint 解码 varint,返回值和消耗的字节数
|
|
func decodeVarint(data []byte) (uint64, int) {
|
|
var val uint64
|
|
var shift uint
|
|
for i, b := range data {
|
|
val |= uint64(b&0x7f) << shift
|
|
if b&0x80 == 0 {
|
|
return val, i + 1
|
|
}
|
|
shift += 7
|
|
if shift >= 64 {
|
|
return 0, 0
|
|
}
|
|
}
|
|
return 0, 0
|
|
}
|
|
|
|
// extractBytesField 从 protobuf 消息中提取指定字段的 bytes 数据
|
|
func extractBytesField(msg []byte, targetField int) ([]byte, error) {
|
|
pos := 0
|
|
for pos < len(msg) {
|
|
tag, n := decodeVarint(msg[pos:])
|
|
if n == 0 {
|
|
break
|
|
}
|
|
pos += n
|
|
|
|
fieldNum := int(tag >> 3)
|
|
wireType := int(tag & 0x07)
|
|
|
|
switch wireType {
|
|
case 0: // varint
|
|
_, n = decodeVarint(msg[pos:])
|
|
if n == 0 {
|
|
return nil, fmt.Errorf("varint 解码失败")
|
|
}
|
|
pos += n
|
|
case 2: // length-delimited
|
|
length, n := decodeVarint(msg[pos:])
|
|
if n == 0 {
|
|
return nil, fmt.Errorf("length 解码失败")
|
|
}
|
|
pos += n
|
|
if pos+int(length) > len(msg) {
|
|
return nil, fmt.Errorf("数据越界: pos=%d, len=%d, msg=%d", pos, length, len(msg))
|
|
}
|
|
if fieldNum == targetField {
|
|
return msg[pos : pos+int(length)], nil
|
|
}
|
|
pos += int(length)
|
|
case 5: // 32-bit
|
|
pos += 4
|
|
case 1: // 64-bit
|
|
pos += 8
|
|
default:
|
|
return nil, fmt.Errorf("未知 wire type: %d", wireType)
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("字段 %d 未找到", targetField)
|
|
}
|
|
|
|
// extractStringField 从 protobuf 消息中提取指定字段的 string 值
|
|
func extractStringField(msg []byte, targetField int) (string, error) {
|
|
data, err := extractBytesField(msg, targetField)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|