--- name: oauth2_exchange kind: function lang: go domain: infra version: "1.0.0" purity: impure signature: "func Oauth2Exchange(config OAuthConfig, code string) (OAuthTokens, error)" description: "Intercambia un authorization code por tokens OAuth2. POST al TokenURL del proveedor con grant_type=authorization_code y las credenciales del cliente. Retorna OAuthTokens con AccessToken, RefreshToken y ExpiresAt calculado." tags: [oauth, oauth2, auth, token, exchange, http, infra] uses_functions: [] uses_types: [OAuthConfig_go_infra, OAuthTokens_go_infra] returns: [OAuthTokens_go_infra] returns_optional: false error_type: error_go_core imports: [encoding/json, fmt, io, net/http, net/url, strings, time] params: - name: config desc: "OAuthConfig del proveedor con ClientID, ClientSecret, TokenURL y RedirectURL" - name: code desc: "authorization code recibido en el callback tras redirigir al usuario a la URL de Oauth2AuthURL" output: "OAuthTokens con access/refresh tokens. ExpiresAt = now + expires_in del proveedor" tested: true tests: ["intercambia code por tokens contra mock server", "rechaza code vacio", "propaga error si proveedor devuelve error"] test_file_path: "functions/infra/oauth2_exchange_test.go" file_path: "functions/infra/oauth2_exchange.go" --- ## Ejemplo ```go code := r.URL.Query().Get("code") state := r.URL.Query().Get("state") // Validar state contra el guardado en cookie/session... tokens, err := Oauth2Exchange(googleConfig, code) if err != nil { HTTPErrorResponse(w, HTTPError{Status: 500, Code: "oauth_error", Message: err.Error()}) return } // Usar tokens.AccessToken para llamar a APIs del proveedor ``` ## Notas Impura — hace POST HTTP al TokenURL con timeout de 30s, y usa `time.Now()` para calcular ExpiresAt. El body es application/x-www-form-urlencoded (estandar OAuth2). Si el proveedor retorna JSON con campo `error` se wrappea en un error descriptivo. El ClientSecret se envia en el body (no en header Authorization Basic) para compatibilidad amplia — la mayoria de proveedores aceptan ambos. NO valida el state anti-CSRF: eso debe hacerlo el handler del callback antes de llamar a Oauth2Exchange.