feat: tipos auth (JWTClaims, Session, OAuthConfig, OAuthTokens, Permission, Role)
Fase 1 del issue 0010 — tipos base del sistema de auth en dominio infra. Define las estructuras que usaran jwt_*, session_*, oauth2_* y rbac_*. Añade dep golang.org/x/crypto/bcrypt para el hashing de passwords.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: JWTClaims
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
algebraic: product
|
||||
definition: |
|
||||
type JWTClaims struct {
|
||||
Subject string `json:"sub"`
|
||||
Issuer string `json:"iss,omitempty"`
|
||||
Audience string `json:"aud,omitempty"`
|
||||
ExpiresAt int64 `json:"exp"`
|
||||
IssuedAt int64 `json:"iat"`
|
||||
Custom map[string]any `json:"custom,omitempty"`
|
||||
}
|
||||
description: "Claims de un JSON Web Token. Incluye los campos registrados (sub, iss, aud, exp, iat) y un mapa Custom libre para claims de aplicacion como role o email."
|
||||
tags: [jwt, auth, token, claims, infra]
|
||||
uses_types: []
|
||||
file_path: "functions/infra/jwt_claims.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
claims := JWTClaims{
|
||||
Subject: "user-123",
|
||||
Issuer: "my-api",
|
||||
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
|
||||
Custom: map[string]any{
|
||||
"role": "admin",
|
||||
"email": "alice@example.com",
|
||||
},
|
||||
}
|
||||
token, _ := JWTGenerate(claims, secret)
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Tipo producto — los campos estandar cubren RFC 7519. ExpiresAt y IssuedAt son Unix epoch seconds. JWTGenerate setea IssuedAt automaticamente si viene en cero. Custom se serializa bajo la clave "custom" en el payload JSON para evitar colisiones con claims registrados. Para leer valores custom de forma segura tras JWTValidate: `v, ok := claims.Custom["role"].(string)`.
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: OAuthConfig
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
algebraic: product
|
||||
definition: |
|
||||
type OAuthConfig struct {
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
AuthURL string `json:"auth_url"`
|
||||
TokenURL string `json:"token_url"`
|
||||
RedirectURL string `json:"redirect_url"`
|
||||
Scopes []string `json:"scopes"`
|
||||
}
|
||||
description: "Configuracion de un proveedor OAuth2. Contiene credenciales del cliente, endpoints de autorizacion/token, redirect URI y scopes solicitados."
|
||||
tags: [oauth, oauth2, auth, config, infra]
|
||||
uses_types: []
|
||||
file_path: "functions/infra/oauth_config.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
google := OAuthConfig{
|
||||
ClientID: os.Getenv("GOOGLE_CLIENT_ID"),
|
||||
ClientSecret: os.Getenv("GOOGLE_CLIENT_SECRET"),
|
||||
AuthURL: "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
TokenURL: "https://oauth2.googleapis.com/token",
|
||||
RedirectURL: "http://localhost:8080/callback",
|
||||
Scopes: []string{"openid", "email", "profile"},
|
||||
}
|
||||
url := Oauth2AuthURL(google, "random-state-123")
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Tipo producto — agrupa todo lo necesario para los tres flujos OAuth2 soportados (auth URL, code exchange, refresh). ClientSecret nunca debe salir del servidor: Oauth2Exchange y Oauth2Refresh lo envian al TokenURL del proveedor, no al cliente. Scopes se concatenan con espacio al construir la URL (`openid email profile`).
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: OAuthTokens
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
algebraic: product
|
||||
definition: |
|
||||
type OAuthTokens struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
}
|
||||
description: "Tokens OAuth2 obtenidos de un flujo de autorizacion. AccessToken es el de corta vida para llamadas a APIs. RefreshToken renueva el access. ExpiresAt es Unix epoch seconds."
|
||||
tags: [oauth, oauth2, token, infra]
|
||||
uses_types: []
|
||||
file_path: "functions/infra/oauth_tokens.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
tokens, _ := Oauth2Exchange(googleConfig, code)
|
||||
if time.Now().Unix() >= tokens.ExpiresAt {
|
||||
tokens, _ = Oauth2Refresh(googleConfig, tokens.RefreshToken)
|
||||
}
|
||||
req.Header.Set("Authorization", tokens.TokenType + " " + tokens.AccessToken)
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Tipo producto — producto del flujo OAuth2. AccessToken tipicamente expira en 1h. RefreshToken puede durar dias/meses segun el proveedor. TokenType suele ser "Bearer". ExpiresAt se calcula como `time.Now().Unix() + expires_in` al parsear la respuesta del proveedor, asi el consumidor solo compara con `time.Now().Unix()` para saber si renovar.
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Permission
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
algebraic: product
|
||||
definition: |
|
||||
type Permission struct {
|
||||
Resource string `json:"resource"`
|
||||
Action string `json:"action"`
|
||||
}
|
||||
description: "Permiso RBAC: accion sobre un recurso. Par (resource, action) evaluado por RBACCheck contra los permisos de un rol."
|
||||
tags: [rbac, permission, auth, infra]
|
||||
uses_types: []
|
||||
file_path: "functions/infra/permission.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
p := Permission{Resource: "users", Action: "delete"}
|
||||
if RBACCheck(roles, "admin", p) {
|
||||
// el rol admin puede borrar usuarios
|
||||
}
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Tipo producto — Resource y Action son strings libres, decididos por la app. Convencion: Resource en plural snake_case (`users`, `articles`, `billing_invoices`), Action verbo minusculas (`read`, `write`, `delete`, `admin`). Los wildcards `*` no se interpretan — si quieres "todas las acciones" define una Permission explicita por cada una en el rol, o crea un rol superadmin fuera del sistema RBAC.
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: Role
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
algebraic: product
|
||||
definition: |
|
||||
type Role struct {
|
||||
Name string `json:"name"`
|
||||
Permissions []Permission `json:"permissions"`
|
||||
}
|
||||
description: "Rol RBAC con nombre y lista de permisos. Los roles son datos puros — cada app decide donde los almacena (hardcoded, JSON, SQLite)."
|
||||
tags: [rbac, role, auth, infra]
|
||||
uses_types: [Permission_go_infra]
|
||||
file_path: "functions/infra/role.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
roles := []Role{
|
||||
{
|
||||
Name: "admin",
|
||||
Permissions: []Permission{
|
||||
{Resource: "users", Action: "read"},
|
||||
{Resource: "users", Action: "write"},
|
||||
{Resource: "users", Action: "delete"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "viewer",
|
||||
Permissions: []Permission{
|
||||
{Resource: "users", Action: "read"},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Tipo producto — Name identifica el rol por nombre (coincide con el valor de `claims.Custom["role"]` para RBACMiddleware). Permissions es la lista completa de acciones que el rol puede realizar. No hay herencia entre roles: si admin debe tener todo lo de viewer, hay que incluir esos permisos explicitamente en la definicion de admin. Esta es una decision consciente para mantener la evaluacion lineal y predecible.
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: Session
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
algebraic: product
|
||||
definition: |
|
||||
type Session struct {
|
||||
Token string `json:"token"`
|
||||
UserID string `json:"user_id"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
description: "Sesion de usuario almacenada en SQLite. Alternativa server-side a JWT para apps con estado. Token es 32 bytes hex opacos generados con crypto/rand."
|
||||
tags: [session, auth, sqlite, token, infra]
|
||||
uses_types: []
|
||||
file_path: "functions/infra/session.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
session, _ := SessionCreate(db, "user-123", 24*time.Hour, map[string]any{
|
||||
"role": "admin",
|
||||
"email": "alice@example.com",
|
||||
})
|
||||
// session.Token es el valor opaco a enviar al cliente
|
||||
w.Header().Set("X-Session-Token", session.Token)
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Tipo producto — Token identifica la sesion en la tabla SQLite. UserID vincula la sesion al usuario. ExpiresAt y CreatedAt son Unix epoch seconds. Metadata es un mapa libre que se serializa a JSON en la columna metadata de la tabla sessions. A diferencia de JWT, Session requiere una query a la BD para validar en cada request — a cambio permite invalidacion inmediata (DELETE FROM sessions WHERE token = ?).
|
||||
Reference in New Issue
Block a user