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,13 @@
|
||||
package infra
|
||||
|
||||
// JWTClaims contiene claims estandar y custom para un JWT.
|
||||
// Incluye los campos registrados mas comunes (sub, iss, aud, exp, iat)
|
||||
// y un mapa libre `Custom` para claims de aplicacion (ej: role, email).
|
||||
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"`
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package infra
|
||||
|
||||
// OAuthConfig contiene la configuracion de un proveedor OAuth2.
|
||||
// Los Scopes se concatenan con espacio al construir la URL de autorizacion.
|
||||
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"`
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package infra
|
||||
|
||||
// OAuthTokens contiene los tokens obtenidos de un flujo OAuth2.
|
||||
// ExpiresAt es Unix epoch seconds calculado a partir de expires_in del proveedor.
|
||||
type OAuthTokens struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package infra
|
||||
|
||||
// Permission representa una accion sobre un recurso.
|
||||
// Ejemplo: {Resource: "users", Action: "delete"}.
|
||||
type Permission struct {
|
||||
Resource string `json:"resource"`
|
||||
Action string `json:"action"`
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package infra
|
||||
|
||||
// Role agrupa permisos bajo un nombre.
|
||||
// Ejemplo: {Name: "admin", Permissions: [{Resource:"users", Action:"delete"}, ...]}.
|
||||
type Role struct {
|
||||
Name string `json:"name"`
|
||||
Permissions []Permission `json:"permissions"`
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package infra
|
||||
|
||||
// Session representa una sesion de usuario almacenada en SQLite.
|
||||
// Token es un valor aleatorio opaco (32 bytes hex = 64 chars).
|
||||
// ExpiresAt y CreatedAt son Unix epoch seconds.
|
||||
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"`
|
||||
}
|
||||
@@ -11,8 +11,9 @@ require (
|
||||
github.com/jackc/pgx/v5 v5.9.1
|
||||
github.com/marcboeker/go-duckdb v1.8.5
|
||||
github.com/mattn/go-sqlite3 v1.14.37
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/sync v0.20.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
nhooyr.io/websocket v1.8.17
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -59,10 +60,10 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.41.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
golang.org/x/mod v0.34.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/tools v0.43.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
nhooyr.io/websocket v1.8.17 // indirect
|
||||
)
|
||||
|
||||
@@ -159,8 +159,8 @@ golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -170,8 +170,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -180,21 +180,23 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c h1:6a8FdnNk6bTXBjR4AGKFgUKuo+7GnR3FX5L7CbveeZc=
|
||||
golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -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