Files
xiaolajiaoyyds 6f71df4849 Initial open-source release
多平台账号批量注册系统,支持 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
2026-03-22 03:44:42 +08:00

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
}