oauth.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. package antigravity
  2. import (
  3. "context"
  4. "crypto/rand"
  5. "crypto/sha256"
  6. "encoding/base64"
  7. "encoding/hex"
  8. "fmt"
  9. "net/http"
  10. "net/url"
  11. "os"
  12. "regexp"
  13. "strings"
  14. "sync"
  15. "time"
  16. infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
  17. )
  18. const (
  19. // Google OAuth 端点
  20. AuthorizeURL = "https://accounts.google.com/o/oauth2/v2/auth"
  21. TokenURL = "https://oauth2.googleapis.com/token"
  22. UserInfoURL = "https://www.googleapis.com/oauth2/v2/userinfo"
  23. // Antigravity OAuth 客户端凭证
  24. ClientID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com"
  25. // AntigravityOAuthClientSecretEnv 是 Antigravity OAuth client_secret 的环境变量名。
  26. AntigravityOAuthClientSecretEnv = "ANTIGRAVITY_OAUTH_CLIENT_SECRET"
  27. // AntigravityUserAgentVersionEnv 是 Antigravity User-Agent 版本号的环境变量名。
  28. AntigravityUserAgentVersionEnv = "ANTIGRAVITY_USER_AGENT_VERSION"
  29. // DefaultUserAgentVersion 是未通过环境变量或后台设置覆盖时使用的默认版本号。
  30. DefaultUserAgentVersion = "2.9.1"
  31. // 固定的 redirect_uri(用户需手动复制 code)
  32. RedirectURI = "http://localhost:8085/callback"
  33. // OAuth scopes
  34. Scopes = "https://www.googleapis.com/auth/cloud-platform " +
  35. "https://www.googleapis.com/auth/userinfo.email " +
  36. "https://www.googleapis.com/auth/userinfo.profile " +
  37. "https://www.googleapis.com/auth/cclog " +
  38. "https://www.googleapis.com/auth/experimentsandconfigs"
  39. // Session 过期时间
  40. SessionTTL = 30 * time.Minute
  41. // URL 可用性 TTL(不可用 URL 的恢复时间)
  42. URLAvailabilityTTL = 5 * time.Minute
  43. // Antigravity API 端点
  44. antigravityProdBaseURL = "https://cloudcode-pa.googleapis.com"
  45. antigravityDailyBaseURL = "https://daily-cloudcode-pa.googleapis.com"
  46. )
  47. var userAgentVersionPattern = regexp.MustCompile(`^\d+\.\d+\.\d+$`)
  48. // UserAgentVersionResolver 提供运行时 User-Agent 版本号覆盖能力。
  49. type UserAgentVersionResolver func(ctx context.Context) string
  50. var (
  51. // defaultUserAgentVersion 可通过环境变量 ANTIGRAVITY_USER_AGENT_VERSION 配置。
  52. defaultUserAgentVersion = DefaultUserAgentVersion
  53. userAgentVersionMu sync.RWMutex
  54. userAgentVersionResolver UserAgentVersionResolver
  55. )
  56. // defaultClientSecret 可通过环境变量 ANTIGRAVITY_OAUTH_CLIENT_SECRET 配置
  57. var defaultClientSecret = "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"
  58. func init() {
  59. // 从环境变量读取版本号,未设置则使用默认值
  60. if version := NormalizeUserAgentVersion(os.Getenv(AntigravityUserAgentVersionEnv)); version != "" {
  61. defaultUserAgentVersion = version
  62. }
  63. // 从环境变量读取 client_secret,未设置则使用默认值
  64. if secret := os.Getenv(AntigravityOAuthClientSecretEnv); secret != "" {
  65. defaultClientSecret = secret
  66. }
  67. }
  68. // NormalizeUserAgentVersion 校验并归一化 Antigravity User-Agent 版本号。
  69. func NormalizeUserAgentVersion(version string) string {
  70. version = strings.TrimSpace(version)
  71. if version == "" || !userAgentVersionPattern.MatchString(version) {
  72. return ""
  73. }
  74. return version
  75. }
  76. // GetDefaultUserAgentVersion 返回配置文件/环境变量层面的默认版本号。
  77. func GetDefaultUserAgentVersion() string {
  78. return defaultUserAgentVersion
  79. }
  80. // SetUserAgentVersionResolver 设置运行时版本号解析器,通常由后台 settings 注入。
  81. func SetUserAgentVersionResolver(resolver UserAgentVersionResolver) {
  82. userAgentVersionMu.Lock()
  83. defer userAgentVersionMu.Unlock()
  84. userAgentVersionResolver = resolver
  85. }
  86. // GetUserAgentVersionForContext 返回当前请求应使用的 Antigravity 版本号。
  87. func GetUserAgentVersionForContext(ctx context.Context) string {
  88. if ctx == nil {
  89. ctx = context.Background()
  90. }
  91. userAgentVersionMu.RLock()
  92. resolver := userAgentVersionResolver
  93. userAgentVersionMu.RUnlock()
  94. if resolver != nil {
  95. if version := NormalizeUserAgentVersion(resolver(ctx)); version != "" {
  96. return version
  97. }
  98. }
  99. return defaultUserAgentVersion
  100. }
  101. // BuildUserAgent 使用指定版本号构造 User-Agent;版本为空或非法时回退默认值。
  102. func BuildUserAgent(version string) string {
  103. if normalized := NormalizeUserAgentVersion(version); normalized != "" {
  104. return fmt.Sprintf("antigravity/%s windows/amd64", normalized)
  105. }
  106. return fmt.Sprintf("antigravity/%s windows/amd64", defaultUserAgentVersion)
  107. }
  108. // GetUserAgentForContext 返回当前请求应使用的 User-Agent。
  109. func GetUserAgentForContext(ctx context.Context) string {
  110. return BuildUserAgent(GetUserAgentVersionForContext(ctx))
  111. }
  112. // GetUserAgent 返回当前配置的 User-Agent。
  113. func GetUserAgent() string {
  114. return GetUserAgentForContext(context.Background())
  115. }
  116. func getClientSecret() (string, error) {
  117. if v := strings.TrimSpace(defaultClientSecret); v != "" {
  118. return v, nil
  119. }
  120. return "", infraerrors.Newf(http.StatusBadRequest, "ANTIGRAVITY_OAUTH_CLIENT_SECRET_MISSING", "missing antigravity oauth client_secret; set %s", AntigravityOAuthClientSecretEnv)
  121. }
  122. // BaseURLs 定义 Antigravity API 端点(与 Antigravity-Manager 保持一致)
  123. var BaseURLs = []string{
  124. antigravityProdBaseURL, // prod (优先)
  125. antigravityDailyBaseURL, // daily sandbox (备用)
  126. }
  127. // BaseURL 默认 URL(保持向后兼容)
  128. var BaseURL = BaseURLs[0]
  129. // ForwardBaseURLs 返回 API 转发用的 URL 顺序(daily 优先)
  130. func ForwardBaseURLs() []string {
  131. if len(BaseURLs) == 0 {
  132. return nil
  133. }
  134. urls := append([]string(nil), BaseURLs...)
  135. dailyIndex := -1
  136. for i, url := range urls {
  137. if url == antigravityDailyBaseURL {
  138. dailyIndex = i
  139. break
  140. }
  141. }
  142. if dailyIndex <= 0 {
  143. return urls
  144. }
  145. reordered := make([]string, 0, len(urls))
  146. reordered = append(reordered, urls[dailyIndex])
  147. for i, url := range urls {
  148. if i == dailyIndex {
  149. continue
  150. }
  151. reordered = append(reordered, url)
  152. }
  153. return reordered
  154. }
  155. // URLAvailability 管理 URL 可用性状态(带 TTL 自动恢复和动态优先级)
  156. type URLAvailability struct {
  157. mu sync.RWMutex
  158. unavailable map[string]time.Time // URL -> 恢复时间
  159. ttl time.Duration
  160. lastSuccess string // 最近成功请求的 URL,优先使用
  161. }
  162. // DefaultURLAvailability 全局 URL 可用性管理器
  163. var DefaultURLAvailability = NewURLAvailability(URLAvailabilityTTL)
  164. // NewURLAvailability 创建 URL 可用性管理器
  165. func NewURLAvailability(ttl time.Duration) *URLAvailability {
  166. return &URLAvailability{
  167. unavailable: make(map[string]time.Time),
  168. ttl: ttl,
  169. }
  170. }
  171. // MarkUnavailable 标记 URL 临时不可用
  172. func (u *URLAvailability) MarkUnavailable(url string) {
  173. u.mu.Lock()
  174. defer u.mu.Unlock()
  175. u.unavailable[url] = time.Now().Add(u.ttl)
  176. }
  177. // MarkSuccess 标记 URL 请求成功,将其设为优先使用
  178. func (u *URLAvailability) MarkSuccess(url string) {
  179. u.mu.Lock()
  180. defer u.mu.Unlock()
  181. u.lastSuccess = url
  182. // 成功后清除该 URL 的不可用标记
  183. delete(u.unavailable, url)
  184. }
  185. // IsAvailable 检查 URL 是否可用
  186. func (u *URLAvailability) IsAvailable(url string) bool {
  187. u.mu.RLock()
  188. defer u.mu.RUnlock()
  189. expiry, exists := u.unavailable[url]
  190. if !exists {
  191. return true
  192. }
  193. return time.Now().After(expiry)
  194. }
  195. // GetAvailableURLs 返回可用的 URL 列表
  196. // 最近成功的 URL 优先,其他按默认顺序
  197. func (u *URLAvailability) GetAvailableURLs() []string {
  198. return u.GetAvailableURLsWithBase(BaseURLs)
  199. }
  200. // GetAvailableURLsWithBase 返回可用的 URL 列表(使用自定义顺序)
  201. // 最近成功的 URL 优先,其他按传入顺序
  202. func (u *URLAvailability) GetAvailableURLsWithBase(baseURLs []string) []string {
  203. u.mu.RLock()
  204. defer u.mu.RUnlock()
  205. now := time.Now()
  206. result := make([]string, 0, len(baseURLs))
  207. // 如果有最近成功的 URL 且可用,放在最前面
  208. if u.lastSuccess != "" {
  209. found := false
  210. for _, url := range baseURLs {
  211. if url == u.lastSuccess {
  212. found = true
  213. break
  214. }
  215. }
  216. if found {
  217. expiry, exists := u.unavailable[u.lastSuccess]
  218. if !exists || now.After(expiry) {
  219. result = append(result, u.lastSuccess)
  220. }
  221. }
  222. }
  223. // 添加其他可用的 URL(按传入顺序)
  224. for _, url := range baseURLs {
  225. // 跳过已添加的 lastSuccess
  226. if url == u.lastSuccess {
  227. continue
  228. }
  229. expiry, exists := u.unavailable[url]
  230. if !exists || now.After(expiry) {
  231. result = append(result, url)
  232. }
  233. }
  234. return result
  235. }
  236. // OAuthSession 保存 OAuth 授权流程的临时状态
  237. type OAuthSession struct {
  238. State string `json:"state"`
  239. CodeVerifier string `json:"code_verifier"`
  240. ProxyURL string `json:"proxy_url,omitempty"`
  241. CreatedAt time.Time `json:"created_at"`
  242. }
  243. // SessionStore OAuth session 存储
  244. type SessionStore struct {
  245. mu sync.RWMutex
  246. sessions map[string]*OAuthSession
  247. stopCh chan struct{}
  248. }
  249. func NewSessionStore() *SessionStore {
  250. store := &SessionStore{
  251. sessions: make(map[string]*OAuthSession),
  252. stopCh: make(chan struct{}),
  253. }
  254. go store.cleanup()
  255. return store
  256. }
  257. func (s *SessionStore) Set(sessionID string, session *OAuthSession) {
  258. s.mu.Lock()
  259. defer s.mu.Unlock()
  260. s.sessions[sessionID] = session
  261. }
  262. func (s *SessionStore) Get(sessionID string) (*OAuthSession, bool) {
  263. s.mu.RLock()
  264. defer s.mu.RUnlock()
  265. session, ok := s.sessions[sessionID]
  266. if !ok {
  267. return nil, false
  268. }
  269. if time.Since(session.CreatedAt) > SessionTTL {
  270. return nil, false
  271. }
  272. return session, true
  273. }
  274. func (s *SessionStore) Delete(sessionID string) {
  275. s.mu.Lock()
  276. defer s.mu.Unlock()
  277. delete(s.sessions, sessionID)
  278. }
  279. func (s *SessionStore) Stop() {
  280. select {
  281. case <-s.stopCh:
  282. return
  283. default:
  284. close(s.stopCh)
  285. }
  286. }
  287. func (s *SessionStore) cleanup() {
  288. ticker := time.NewTicker(5 * time.Minute)
  289. defer ticker.Stop()
  290. for {
  291. select {
  292. case <-s.stopCh:
  293. return
  294. case <-ticker.C:
  295. s.mu.Lock()
  296. for id, session := range s.sessions {
  297. if time.Since(session.CreatedAt) > SessionTTL {
  298. delete(s.sessions, id)
  299. }
  300. }
  301. s.mu.Unlock()
  302. }
  303. }
  304. }
  305. func GenerateRandomBytes(n int) ([]byte, error) {
  306. b := make([]byte, n)
  307. _, err := rand.Read(b)
  308. if err != nil {
  309. return nil, err
  310. }
  311. return b, nil
  312. }
  313. func GenerateState() (string, error) {
  314. bytes, err := GenerateRandomBytes(32)
  315. if err != nil {
  316. return "", err
  317. }
  318. return base64URLEncode(bytes), nil
  319. }
  320. func GenerateSessionID() (string, error) {
  321. bytes, err := GenerateRandomBytes(16)
  322. if err != nil {
  323. return "", err
  324. }
  325. return hex.EncodeToString(bytes), nil
  326. }
  327. func GenerateCodeVerifier() (string, error) {
  328. bytes, err := GenerateRandomBytes(32)
  329. if err != nil {
  330. return "", err
  331. }
  332. return base64URLEncode(bytes), nil
  333. }
  334. func GenerateCodeChallenge(verifier string) string {
  335. hash := sha256.Sum256([]byte(verifier))
  336. return base64URLEncode(hash[:])
  337. }
  338. func base64URLEncode(data []byte) string {
  339. return strings.TrimRight(base64.URLEncoding.EncodeToString(data), "=")
  340. }
  341. // BuildAuthorizationURL 构建 Google OAuth 授权 URL
  342. func BuildAuthorizationURL(state, codeChallenge string) string {
  343. params := url.Values{}
  344. params.Set("client_id", ClientID)
  345. params.Set("redirect_uri", RedirectURI)
  346. params.Set("response_type", "code")
  347. params.Set("scope", Scopes)
  348. params.Set("state", state)
  349. params.Set("code_challenge", codeChallenge)
  350. params.Set("code_challenge_method", "S256")
  351. params.Set("access_type", "offline")
  352. params.Set("prompt", "consent")
  353. params.Set("include_granted_scopes", "true")
  354. return fmt.Sprintf("%s?%s", AuthorizeURL, params.Encode())
  355. }