Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
378
backend/internal/auth/antigravity/auth.go
Normal file
378
backend/internal/auth/antigravity/auth.go
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
// Package antigravity provides OAuth2 authentication functionality for the Antigravity provider.
|
||||
package antigravity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/misc"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// TokenResponse represents OAuth token response from Google
|
||||
type TokenResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
TokenType string `json:"token_type"`
|
||||
}
|
||||
|
||||
// userInfo represents Google user profile
|
||||
type userInfo struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// AntigravityAuth handles Antigravity OAuth authentication
|
||||
type AntigravityAuth struct {
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewAntigravityAuth creates a new Antigravity auth service.
|
||||
func NewAntigravityAuth(cfg *config.Config, httpClient *http.Client) *AntigravityAuth {
|
||||
if cfg == nil {
|
||||
cfg = &config.Config{}
|
||||
}
|
||||
if httpClient != nil {
|
||||
return &AntigravityAuth{httpClient: httpClient}
|
||||
}
|
||||
return &AntigravityAuth{
|
||||
httpClient: util.SetProxy(&cfg.SDKConfig, &http.Client{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (o *AntigravityAuth) shortUserAgent() string {
|
||||
return misc.AntigravityRequestUserAgent("")
|
||||
}
|
||||
|
||||
func (o *AntigravityAuth) nodeUserAgent() string {
|
||||
return misc.AntigravityOnboardUserUserAgent("")
|
||||
}
|
||||
|
||||
func antigravityLoadCodeAssistMetadata() map[string]string {
|
||||
return map[string]string{
|
||||
"ideType": "ANTIGRAVITY",
|
||||
}
|
||||
}
|
||||
|
||||
func antigravityControlPlaneMetadata(userAgent string) map[string]string {
|
||||
return map[string]string{
|
||||
"ide_type": "ANTIGRAVITY",
|
||||
"ide_version": misc.AntigravityVersionFromUserAgent(userAgent),
|
||||
"ide_name": "antigravity",
|
||||
}
|
||||
}
|
||||
|
||||
func extractCloudaicompanionProject(data map[string]any) string {
|
||||
if data == nil {
|
||||
return ""
|
||||
}
|
||||
for _, key := range []string{"cloudaicompanionProject", "projectId", "project"} {
|
||||
switch value := data[key].(type) {
|
||||
case string:
|
||||
if trimmed := strings.TrimSpace(value); trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
case map[string]any:
|
||||
if id, ok := value["id"].(string); ok {
|
||||
if trimmed := strings.TrimSpace(id); trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func defaultAntigravityTierID(loadResp map[string]any) string {
|
||||
if tiers, okTiers := loadResp["allowedTiers"].([]any); okTiers {
|
||||
for _, rawTier := range tiers {
|
||||
tier, okTier := rawTier.(map[string]any)
|
||||
if !okTier {
|
||||
continue
|
||||
}
|
||||
if isDefault, okDefault := tier["isDefault"].(bool); !okDefault || !isDefault {
|
||||
continue
|
||||
}
|
||||
if id, okID := tier["id"].(string); okID {
|
||||
if trimmed := strings.TrimSpace(id); trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if currentTier, okTier := loadResp["currentTier"].(map[string]any); okTier {
|
||||
if id, okID := currentTier["id"].(string); okID {
|
||||
if trimmed := strings.TrimSpace(id); trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
}
|
||||
}
|
||||
return "free-tier"
|
||||
}
|
||||
|
||||
// BuildAuthURL generates the OAuth authorization URL.
|
||||
func (o *AntigravityAuth) BuildAuthURL(state, redirectURI string) string {
|
||||
if strings.TrimSpace(redirectURI) == "" {
|
||||
redirectURI = fmt.Sprintf("http://localhost:%d/oauth-callback", CallbackPort)
|
||||
}
|
||||
params := url.Values{}
|
||||
params.Set("access_type", "offline")
|
||||
params.Set("client_id", ClientID)
|
||||
params.Set("prompt", "consent")
|
||||
params.Set("redirect_uri", redirectURI)
|
||||
params.Set("response_type", "code")
|
||||
params.Set("scope", strings.Join(Scopes, " "))
|
||||
params.Set("state", state)
|
||||
return AuthEndpoint + "?" + params.Encode()
|
||||
}
|
||||
|
||||
// ExchangeCodeForTokens exchanges authorization code for access and refresh tokens
|
||||
func (o *AntigravityAuth) ExchangeCodeForTokens(ctx context.Context, code, redirectURI string) (*TokenResponse, error) {
|
||||
data := url.Values{}
|
||||
data.Set("code", code)
|
||||
data.Set("client_id", ClientID)
|
||||
data.Set("client_secret", ClientSecret)
|
||||
data.Set("redirect_uri", redirectURI)
|
||||
data.Set("grant_type", "authorization_code")
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, TokenEndpoint, strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("antigravity token exchange: create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp, errDo := o.httpClient.Do(req)
|
||||
if errDo != nil {
|
||||
return nil, fmt.Errorf("antigravity token exchange: execute request: %w", errDo)
|
||||
}
|
||||
defer func() {
|
||||
if errClose := resp.Body.Close(); errClose != nil {
|
||||
log.Errorf("antigravity token exchange: close body error: %v", errClose)
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
bodyBytes, errRead := io.ReadAll(io.LimitReader(resp.Body, 8<<10))
|
||||
if errRead != nil {
|
||||
return nil, fmt.Errorf("antigravity token exchange: read response: %w", errRead)
|
||||
}
|
||||
body := strings.TrimSpace(string(bodyBytes))
|
||||
if body == "" {
|
||||
return nil, fmt.Errorf("antigravity token exchange: request failed: status %d", resp.StatusCode)
|
||||
}
|
||||
return nil, fmt.Errorf("antigravity token exchange: request failed: status %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
var token TokenResponse
|
||||
if errDecode := json.NewDecoder(resp.Body).Decode(&token); errDecode != nil {
|
||||
return nil, fmt.Errorf("antigravity token exchange: decode response: %w", errDecode)
|
||||
}
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
// FetchUserInfo retrieves user email from Google
|
||||
func (o *AntigravityAuth) FetchUserInfo(ctx context.Context, accessToken string) (string, error) {
|
||||
accessToken = strings.TrimSpace(accessToken)
|
||||
if accessToken == "" {
|
||||
return "", fmt.Errorf("antigravity userinfo: missing access token")
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, UserInfoEndpoint, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("antigravity userinfo: create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
req.Header.Set("User-Agent", o.shortUserAgent())
|
||||
|
||||
resp, errDo := o.httpClient.Do(req)
|
||||
if errDo != nil {
|
||||
return "", fmt.Errorf("antigravity userinfo: execute request: %w", errDo)
|
||||
}
|
||||
defer func() {
|
||||
if errClose := resp.Body.Close(); errClose != nil {
|
||||
log.Errorf("antigravity userinfo: close body error: %v", errClose)
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
bodyBytes, errRead := io.ReadAll(io.LimitReader(resp.Body, 8<<10))
|
||||
if errRead != nil {
|
||||
return "", fmt.Errorf("antigravity userinfo: read response: %w", errRead)
|
||||
}
|
||||
body := strings.TrimSpace(string(bodyBytes))
|
||||
if body == "" {
|
||||
return "", fmt.Errorf("antigravity userinfo: request failed: status %d", resp.StatusCode)
|
||||
}
|
||||
return "", fmt.Errorf("antigravity userinfo: request failed: status %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
var info userInfo
|
||||
if errDecode := json.NewDecoder(resp.Body).Decode(&info); errDecode != nil {
|
||||
return "", fmt.Errorf("antigravity userinfo: decode response: %w", errDecode)
|
||||
}
|
||||
email := strings.TrimSpace(info.Email)
|
||||
if email == "" {
|
||||
return "", fmt.Errorf("antigravity userinfo: response missing email")
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
|
||||
// FetchProjectID retrieves the project ID for the authenticated user via loadCodeAssist
|
||||
func (o *AntigravityAuth) FetchProjectID(ctx context.Context, accessToken string) (string, error) {
|
||||
userAgent := o.shortUserAgent()
|
||||
loadReqBody := map[string]any{
|
||||
"metadata": antigravityLoadCodeAssistMetadata(),
|
||||
}
|
||||
|
||||
rawBody, errMarshal := json.Marshal(loadReqBody)
|
||||
if errMarshal != nil {
|
||||
return "", fmt.Errorf("marshal request body: %w", errMarshal)
|
||||
}
|
||||
|
||||
endpointURL := fmt.Sprintf("%s/%s:loadCodeAssist", APIEndpoint, APIVersion)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpointURL, strings.NewReader(string(rawBody)))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
|
||||
resp, errDo := o.httpClient.Do(req)
|
||||
if errDo != nil {
|
||||
return "", fmt.Errorf("execute request: %w", errDo)
|
||||
}
|
||||
defer func() {
|
||||
if errClose := resp.Body.Close(); errClose != nil {
|
||||
log.Errorf("antigravity loadCodeAssist: close body error: %v", errClose)
|
||||
}
|
||||
}()
|
||||
|
||||
bodyBytes, errRead := io.ReadAll(resp.Body)
|
||||
if errRead != nil {
|
||||
return "", fmt.Errorf("read response: %w", errRead)
|
||||
}
|
||||
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
return "", fmt.Errorf("request failed with status %d: %s", resp.StatusCode, strings.TrimSpace(string(bodyBytes)))
|
||||
}
|
||||
|
||||
var loadResp map[string]any
|
||||
if errDecode := json.Unmarshal(bodyBytes, &loadResp); errDecode != nil {
|
||||
return "", fmt.Errorf("decode response: %w", errDecode)
|
||||
}
|
||||
|
||||
projectID := extractCloudaicompanionProject(loadResp)
|
||||
|
||||
if projectID == "" {
|
||||
projectID, err = o.OnboardUser(ctx, accessToken, defaultAntigravityTierID(loadResp))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if projectID == "" {
|
||||
return "", fmt.Errorf("project id not found in loadCodeAssist or onboardUser response")
|
||||
}
|
||||
return projectID, nil
|
||||
}
|
||||
|
||||
return projectID, nil
|
||||
}
|
||||
|
||||
// OnboardUser attempts to fetch the project ID via onboardUser by polling for completion
|
||||
func (o *AntigravityAuth) OnboardUser(ctx context.Context, accessToken, tierID string) (string, error) {
|
||||
log.Infof("Antigravity: onboarding user with tier: %s", tierID)
|
||||
userAgent := o.nodeUserAgent()
|
||||
requestBody := map[string]any{
|
||||
"tier_id": tierID,
|
||||
"metadata": antigravityControlPlaneMetadata(userAgent),
|
||||
}
|
||||
|
||||
rawBody, errMarshal := json.Marshal(requestBody)
|
||||
if errMarshal != nil {
|
||||
return "", fmt.Errorf("marshal request body: %w", errMarshal)
|
||||
}
|
||||
|
||||
maxAttempts := 5
|
||||
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
||||
log.Debugf("Polling attempt %d/%d", attempt, maxAttempts)
|
||||
|
||||
reqCtx := ctx
|
||||
var cancel context.CancelFunc
|
||||
if reqCtx == nil {
|
||||
reqCtx = context.Background()
|
||||
}
|
||||
reqCtx, cancel = context.WithTimeout(reqCtx, 30*time.Second)
|
||||
|
||||
endpointURL := fmt.Sprintf("%s/%s:onboardUser", DailyAPIEndpoint, APIVersion)
|
||||
req, errRequest := http.NewRequestWithContext(reqCtx, http.MethodPost, endpointURL, strings.NewReader(string(rawBody)))
|
||||
if errRequest != nil {
|
||||
cancel()
|
||||
return "", fmt.Errorf("create request: %w", errRequest)
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
req.Header.Set("X-Goog-Api-Client", misc.AntigravityGoogAPIClientUA)
|
||||
|
||||
resp, errDo := o.httpClient.Do(req)
|
||||
if errDo != nil {
|
||||
cancel()
|
||||
return "", fmt.Errorf("execute request: %w", errDo)
|
||||
}
|
||||
|
||||
bodyBytes, errRead := io.ReadAll(resp.Body)
|
||||
if errClose := resp.Body.Close(); errClose != nil {
|
||||
log.Errorf("close body error: %v", errClose)
|
||||
}
|
||||
cancel()
|
||||
|
||||
if errRead != nil {
|
||||
return "", fmt.Errorf("read response: %w", errRead)
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
var data map[string]any
|
||||
if errDecode := json.Unmarshal(bodyBytes, &data); errDecode != nil {
|
||||
return "", fmt.Errorf("decode response: %w", errDecode)
|
||||
}
|
||||
|
||||
if done, okDone := data["done"].(bool); okDone && done {
|
||||
projectID := ""
|
||||
if responseData, okResp := data["response"].(map[string]any); okResp {
|
||||
projectID = extractCloudaicompanionProject(responseData)
|
||||
}
|
||||
|
||||
if projectID != "" {
|
||||
log.Infof("Successfully fetched project_id: %s", util.HideAPIKey(projectID))
|
||||
return projectID, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no project_id in response")
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
responsePreview := strings.TrimSpace(string(bodyBytes))
|
||||
if len(responsePreview) > 500 {
|
||||
responsePreview = responsePreview[:500]
|
||||
}
|
||||
|
||||
responseErr := responsePreview
|
||||
if len(responseErr) > 200 {
|
||||
responseErr = responseErr[:200]
|
||||
}
|
||||
return "", fmt.Errorf("http %d: %s", resp.StatusCode, responseErr)
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("onboard user did not complete after %d attempts", maxAttempts)
|
||||
}
|
||||
135
backend/internal/auth/antigravity/auth_test.go
Normal file
135
backend/internal/auth/antigravity/auth_test.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package antigravity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type roundTripperFunc func(*http.Request) (*http.Response, error)
|
||||
|
||||
func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return f(req)
|
||||
}
|
||||
|
||||
func TestFetchProjectIDFromLoadCodeAssist(t *testing.T) {
|
||||
auth := NewAntigravityAuth(nil, &http.Client{Transport: roundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.String() != "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist" {
|
||||
t.Fatalf("unexpected request URL: %s", req.URL.String())
|
||||
}
|
||||
assertLoadCodeAssistHeaders(t, req)
|
||||
assertJSONContains(t, req, `"ideType":"ANTIGRAVITY"`)
|
||||
return jsonResponse(`{"cloudaicompanionProject":"cogent-snow-4mnnp"}`), nil
|
||||
})})
|
||||
|
||||
projectID, err := auth.FetchProjectID(context.Background(), "access-token")
|
||||
if err != nil {
|
||||
t.Fatalf("FetchProjectID error: %v", err)
|
||||
}
|
||||
if projectID != "cogent-snow-4mnnp" {
|
||||
t.Fatalf("projectID = %q", projectID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchProjectIDFallsBackToDailyOnboardUser(t *testing.T) {
|
||||
var sawOnboard bool
|
||||
auth := NewAntigravityAuth(nil, &http.Client{Transport: roundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch req.URL.String() {
|
||||
case "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist":
|
||||
assertLoadCodeAssistHeaders(t, req)
|
||||
return jsonResponse(`{"allowedTiers":[{"id":"free-tier","isDefault":true}]}`), nil
|
||||
case "https://daily-cloudcode-pa.googleapis.com/v1internal:onboardUser":
|
||||
sawOnboard = true
|
||||
assertOnboardUserHeaders(t, req)
|
||||
assertJSONContains(t, req, `"tier_id":"free-tier"`)
|
||||
assertJSONContains(t, req, `"ide_type":"ANTIGRAVITY"`)
|
||||
return jsonResponse(`{
|
||||
"done": true,
|
||||
"response": {
|
||||
"cloudaicompanionProject": {
|
||||
"id": "cogent-snow-4mnnp",
|
||||
"name": "cogent-snow-4mnnp",
|
||||
"projectNumber": "22597072101"
|
||||
}
|
||||
}
|
||||
}`), nil
|
||||
default:
|
||||
t.Fatalf("unexpected request URL: %s", req.URL.String())
|
||||
return nil, nil
|
||||
}
|
||||
})})
|
||||
|
||||
projectID, err := auth.FetchProjectID(context.Background(), "access-token")
|
||||
if err != nil {
|
||||
t.Fatalf("FetchProjectID error: %v", err)
|
||||
}
|
||||
if !sawOnboard {
|
||||
t.Fatalf("expected onboardUser fallback")
|
||||
}
|
||||
if projectID != "cogent-snow-4mnnp" {
|
||||
t.Fatalf("projectID = %q", projectID)
|
||||
}
|
||||
}
|
||||
|
||||
func assertLoadCodeAssistHeaders(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
if got := req.Header.Get("Authorization"); got != "Bearer access-token" {
|
||||
t.Fatalf("Authorization = %q", got)
|
||||
}
|
||||
if got := req.Header.Get("Accept"); got != "*/*" {
|
||||
t.Fatalf("Accept = %q", got)
|
||||
}
|
||||
if got := req.Header.Get("X-Goog-Api-Client"); got != "" {
|
||||
t.Fatalf("X-Goog-Api-Client = %q, want empty", got)
|
||||
}
|
||||
userAgent := req.Header.Get("User-Agent")
|
||||
if !strings.HasPrefix(userAgent, "antigravity/hub/") {
|
||||
t.Fatalf("User-Agent = %q", userAgent)
|
||||
}
|
||||
if strings.Contains(userAgent, "google-api-nodejs-client/") {
|
||||
t.Fatalf("User-Agent = %q", userAgent)
|
||||
}
|
||||
}
|
||||
|
||||
func assertOnboardUserHeaders(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
if got := req.Header.Get("Authorization"); got != "Bearer access-token" {
|
||||
t.Fatalf("Authorization = %q", got)
|
||||
}
|
||||
if got := req.Header.Get("Accept"); got != "*/*" {
|
||||
t.Fatalf("Accept = %q", got)
|
||||
}
|
||||
if got := req.Header.Get("X-Goog-Api-Client"); got != "gl-node/22.21.1" {
|
||||
t.Fatalf("X-Goog-Api-Client = %q", got)
|
||||
}
|
||||
userAgent := req.Header.Get("User-Agent")
|
||||
if !strings.HasPrefix(userAgent, "antigravity/hub/") {
|
||||
t.Fatalf("User-Agent = %q", userAgent)
|
||||
}
|
||||
if !strings.Contains(userAgent, "google-api-nodejs-client/10.3.0") {
|
||||
t.Fatalf("User-Agent = %q", userAgent)
|
||||
}
|
||||
}
|
||||
|
||||
func assertJSONContains(t *testing.T, req *http.Request, want string) {
|
||||
t.Helper()
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read body: %v", err)
|
||||
}
|
||||
bodyText := string(body)
|
||||
req.Body = io.NopCloser(strings.NewReader(bodyText))
|
||||
if !strings.Contains(bodyText, want) {
|
||||
t.Fatalf("body missing %s: %s", want, bodyText)
|
||||
}
|
||||
}
|
||||
|
||||
func jsonResponse(body string) *http.Response {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: make(http.Header),
|
||||
Body: io.NopCloser(strings.NewReader(body)),
|
||||
}
|
||||
}
|
||||
32
backend/internal/auth/antigravity/constants.go
Normal file
32
backend/internal/auth/antigravity/constants.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Package antigravity provides OAuth2 authentication functionality for the Antigravity provider.
|
||||
package antigravity
|
||||
|
||||
// OAuth client credentials and configuration
|
||||
const (
|
||||
ClientID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com"
|
||||
ClientSecret = "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"
|
||||
CallbackPort = 51121
|
||||
)
|
||||
|
||||
// Scopes defines the OAuth scopes required for Antigravity authentication
|
||||
var Scopes = []string{
|
||||
"https://www.googleapis.com/auth/cloud-platform",
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
"https://www.googleapis.com/auth/userinfo.profile",
|
||||
"https://www.googleapis.com/auth/cclog",
|
||||
"https://www.googleapis.com/auth/experimentsandconfigs",
|
||||
}
|
||||
|
||||
// OAuth2 endpoints for Google authentication
|
||||
const (
|
||||
TokenEndpoint = "https://oauth2.googleapis.com/token"
|
||||
AuthEndpoint = "https://accounts.google.com/o/oauth2/v2/auth"
|
||||
UserInfoEndpoint = "https://www.googleapis.com/oauth2/v2/userinfo?alt=json"
|
||||
)
|
||||
|
||||
// Antigravity API configuration
|
||||
const (
|
||||
APIEndpoint = "https://cloudcode-pa.googleapis.com"
|
||||
DailyAPIEndpoint = "https://daily-cloudcode-pa.googleapis.com"
|
||||
APIVersion = "v1internal"
|
||||
)
|
||||
16
backend/internal/auth/antigravity/filename.go
Normal file
16
backend/internal/auth/antigravity/filename.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package antigravity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CredentialFileName returns the filename used to persist Antigravity credentials.
|
||||
// It uses the email as a suffix to disambiguate accounts.
|
||||
func CredentialFileName(email string) string {
|
||||
email = strings.TrimSpace(email)
|
||||
if email == "" {
|
||||
return "antigravity.json"
|
||||
}
|
||||
return fmt.Sprintf("antigravity-%s.json", email)
|
||||
}
|
||||
Loading…
Reference in a new issue