package platform import ( "context" "encoding/json" "errors" "fmt" "net/http" "strings" "time" ) // FetchOAuthConfig pulls the provider coordinates from the cdrop backend's // /api/auth/config. The desktop reuses the same OAuth client as the web app // (the client_id published there) and runs its own loopback PKCE flow against // the provider, instead of the browser's in-page redirect + /api/auth/exchange // proxy. apiBase is the backend origin, e.g. https://drop.commilitia.net. func FetchOAuthConfig(ctx context.Context, apiBase string) (OAuthConfig, error) { endpoint := strings.TrimRight(apiBase, "/") + "/api/auth/config" req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return OAuthConfig{}, fmt.Errorf("oauth: build config request: %w", err) } req.Header.Set("Accept", "application/json") client := &http.Client{Timeout: 15 * time.Second} resp, err := client.Do(req) if err != nil { return OAuthConfig{}, fmt.Errorf("oauth: fetch config: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return OAuthConfig{}, fmt.Errorf("oauth: config endpoint status %d", resp.StatusCode) } var c struct { AuthorizeURL string `json:"authorize_url"` TokenURL string `json:"token_url"` ClientID string `json:"client_id"` Scopes string `json:"scopes"` } if err := json.NewDecoder(resp.Body).Decode(&c); err != nil { return OAuthConfig{}, fmt.Errorf("oauth: decode config: %w", err) } cfg := OAuthConfig{ AuthorizeURL: c.AuthorizeURL, TokenURL: c.TokenURL, ClientID: c.ClientID, Scopes: c.Scopes, } if cfg.AuthorizeURL == "" || cfg.TokenURL == "" || cfg.ClientID == "" { return OAuthConfig{}, errors.New("oauth: backend config missing authorize_url / token_url / client_id") } return cfg, nil }