feat: oauth2_auth_url (pure), oauth2_exchange, oauth2_refresh
Fase 4 del issue 0010 — cliente OAuth2 sin golang.org/x/oauth2. - Oauth2AuthURL es pura: solo construye la URL con net/url - Oauth2Exchange/Refresh hacen POST application/x-www-form-urlencoded - ExpiresAt calculado como now + expires_in del proveedor - Refresh conserva el token original si el proveedor no devuelve uno nuevo - Tests con httptest.NewServer como mock del proveedor
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
---
|
||||
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.
|
||||
Reference in New Issue
Block a user