user_oauth2_password

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DefaultAccessTokenTTL     = 30 * time.Minute
	DefaultRefreshIdleTTL     = 72 * time.Hour
	DefaultRefreshAbsoluteTTL = 720 * time.Hour // 30 dias
	DefaultRefreshReuseGrace  = 30 * time.Second
)

TTLs padrão usados na cascata de configuração: valem quando nenhuma variável de ambiente (nova ou legada) é encontrada.

View Source
const (
	RefreshTokenKeyPrefix       = "RT:"     // refresh token ativo (formato novo)
	RefreshTokenUsedKeyPrefix   = "RTUSED:" // par já emitido dentro da graça de reuso
	RefreshTokenTombKeyPrefix   = "RTTOMB:" // marca o token como usado (detecta reuso fora da graça)
	RefreshTokenFamilyKeyPrefix = "RTFAM:"  // SET com os refresh tokens da família
)

Prefixos das chaves Redis do fluxo de refresh seguro (mesmo Redis/DB do storage atual). Tokens legados continuam gravados sem prefixo e lidos em dupla para não derrubar sessões vivas no deploy. Exportados para que os consumidores (e suites de teste) não dupliquem literais sujeitos a drift.

Variables

View Source
var (
	// StorageDBWriter - Oauth tokens redis cache connection
	StorageDBWriter *redis.Client
	// StorageDBReader - Oauth tokens redis cache connection
	StorageDBReader *redis.Client
)

Functions

func AccessTokenTTL

func AccessTokenTTL(cfgs configuration.ConfigurationInterface) (time.Duration, error)

AccessTokenTTL lê o TTL do access token em cascata: OAUTH2_ACCESS_TOKEN_TTL (string com unidade, ex.: "30m") => OAUTH2_ACCESS_TOKEN_EXPIRATION (inteiro em MINUTOS, comportamento legado, loga warn de deprecação) => 30m.

func AuthenticationOauth2PasswordHandler

func AuthenticationOauth2PasswordHandler(c echo.Context) error

func DeleteAccessToken

func DeleteAccessToken(c *bolo.RequestContext, accessToken string) error

func GetAccessToken

func GetAccessToken(accessToken string) (string, error)

func GetOauth2TokenFromAuthorization

func GetOauth2TokenFromAuthorization(authorization string) string

GetOauth2TokenFromAuthorization extrai o token oauth2 do header Authorization. Aceita SOMENTE o prefixo "Bearer": outros esquemas (ex.: "Basic") não carregam um access token oauth2 e devolvem "".

func GetRefreshToken deprecated

func GetRefreshToken(refreshToken string) (string, error)

GetRefreshToken lê a chave legada (sem prefixo) do refresh token.

Deprecated: o formato legado é somente leitura/migração; use RotateRefreshToken/RevokeByRefreshToken, que enxergam os dois formatos.

func HandleRequestAuthentication

func HandleRequestAuthentication(c echo.Context) error

func InitStorage

func InitStorage(app bolo.App)

InitStorage - Start the redis cache connection

func IsPublicRoute

func IsPublicRoute(url string) bool

func NewUnauthorizedTokenHTTPError

func NewUnauthorizedTokenHTTPError(c echo.Context, description string) error

NewUnauthorizedTokenHTTPError monta o 401 do modo strict: registra o header WWW-Authenticate na resposta e devolve o erro compatível com o handler do bolo (que renderiza o corpo no formato BaseErrorResponse para JSON).

func Oauth2FindUserWithToken

func Oauth2FindUserWithToken(accessToken string) (bolo.UserInterface, error)

func ParseTTL

func ParseTTL(value string, fallback time.Duration) (time.Duration, error)

ParseTTL converte um valor de duração com unidade (ex.: "30m", "7d", "2d12h") em time.Duration. Além das unidades de time.ParseDuration aceita o sufixo "d" de dias (7d => 168h). Valor vazio devolve o fallback; valor inválido devolve erro explícito — configuração quebrada não pode ser silenciada.

func RefreshAbsoluteTTL

func RefreshAbsoluteTTL(cfgs configuration.ConfigurationInterface) (time.Duration, error)

RefreshAbsoluteTTL lê o teto absoluto da família de refresh tokens: mesmo com rotações contínuas a família morre nesse prazo (OAUTH2_REFRESH_ABSOLUTE_TTL, default 720h/30 dias).

func RefreshIdleTTL

func RefreshIdleTTL(cfgs configuration.ConfigurationInterface) (time.Duration, error)

RefreshIdleTTL lê o TTL idle do refresh token (renovado a cada rotação) em cascata: OAUTH2_REFRESH_IDLE_TTL (com unidade) => OAUTH2_REFRESH_TOKEN_EXPIRATION (inteiro em MINUTOS, legado) => 72h.

func RefreshReuseGrace

func RefreshReuseGrace(cfgs configuration.ConfigurationInterface) (time.Duration, error)

RefreshReuseGrace lê a janela de graça do reuso idempotente do refresh token: dentro dela, replay do token antigo devolve o MESMO par emitido (OAUTH2_REFRESH_REUSE_GRACE, default 30s).

func RevokeByRefreshToken

func RevokeByRefreshToken(token string) error

RevokeByRefreshToken resolve a família à qual o token pertence (ativo, já usado ou legado) e revoga tudo. Token desconhecido não é erro (best-effort).

func RevokeOauth2TokenHandler

func RevokeOauth2TokenHandler(c echo.Context) error

RevokeOauth2TokenHandler revoga um refresh token (hint "refresh_token" ou ausente) ou um access token (hint "access_token"). Seguindo a RFC 7009, responde 200 SEMPRE para token de formato válido, sem revelar se o token existia; falha de revogação é apenas logada (best-effort).

func RevokeRefreshFamily

func RevokeRefreshFamily(familyID string) error

RevokeRefreshFamily revoga todos os tokens de uma família: refresh tokens ativos, pares em graça e tombstones. Usado em reuso detectado, teto absoluto vencido e revogação explícita (logout/revoke). Todas as chaves são removidas num único DEL multi-key (1 round trip + 1 por índice).

func RotateRefreshToken

func RotateRefreshToken(c echo.Context, refreshToken string) (*TokenPair, *user_models.UserModel, *RefreshError)

RotateRefreshToken troca um refresh token válido por um par novo (formato opaco legado), com rotação single-use e graça de reuso idempotente. Veja RotateRefreshTokenWithFormat para a semântica completa.

func RotateRefreshTokenWithFormat

func RotateRefreshTokenWithFormat(c echo.Context, refreshToken string, format AccessTokenFormat) (*TokenPair, *user_models.UserModel, *RefreshError)

RotateRefreshTokenWithFormat troca um refresh token válido por um par novo no formato pedido, com rotação single-use e graça de reuso idempotente:

  1. RTUSED hit (dentro da graça) => devolve o MESMO par já emitido, sem nova rotação (a aba que perdeu a corrida também recebe 200);
  2. RT: hit (formato novo) ou chave legada sem prefixo => valida o teto absoluto e o usuário, gera par novo, indexa a família, marca o token antigo como usado (RTUSED + RTTOMB) e o consome; o token legado migra para o formato novo;
  3. miss => se houver tombstone é reuso fora da graça (revoga a família e retorna ErrReuseDetected); sem tombstone é sessão vencida por idle (ErrExpiredIdle).

Com FormatJWT o access novo é um JWT stateless (não gravado no Redis); o par publicado na graça (RTUSED) carrega a string JWT sem alteração, então o replay dentro da graça devolve o MESMO JWT. O refresh token do par é sempre opaco — a string de acesso é transparente para graça/famílias.

Erros de infra (Redis fora, dados corrompidos, config inválida) vêm com Kind ErrInternal e NÃO revogam nada — o chamador decide o 500.

func SetAccessToken

func SetAccessToken(c *bolo.RequestContext, accessToken string, value string) error

func SetJWTAccessGenerator

func SetJWTAccessGenerator(gen JWTAccessGenerator)

SetJWTAccessGenerator registra o gerador de access tokens JWT usado por Oauth2GenerateAndSaveTokenJWT e RotateRefreshTokenWithFormat.

func SetRefreshToken

func SetRefreshToken(c *bolo.RequestContext, refreshToken string, value string) error

func Strict401Enabled

func Strict401Enabled(cfgs configuration.ConfigurationInterface) bool

Strict401Enabled indica se o middleware deve responder 401 explícito (com WWW-Authenticate) para access token inexistente/expirado em vez de seguir a requisição como anônima (OAUTH2_STRICT_401, default false).

func ValidUsernamePassword

func ValidUsernamePassword(username, password string) (bool, error)

Types

type AccessTokenFormat

type AccessTokenFormat int

AccessTokenFormat seleciona o formato do access token emitido nos fluxos de geração/rotação de tokens.

const (
	// FormatOpaque: access token opaco (uuid + random) gravado no Redis e
	// validado pelo middleware oauth2TokenAuthentication. Fluxo legado.
	FormatOpaque AccessTokenFormat = iota
	// FormatJWT: access JWT stateless — NÃO é gravado no Redis; a validação é
	// criptográfica (middleware do subplugin oauth2_jwt). Apenas o refresh
	// token mantém estado no Redis.
	FormatJWT
)

type ForbiddenHTTPError

type ForbiddenHTTPError struct {
	Code         int         `json:"code"`
	Message      interface{} `json:"message"`
	Internal     error       `json:"-"` // Stores the error returned by an external dependency
	ErrorMessage string      `json:"error"`
	ErrorContext string      `json:"error_context"`
}

func (*ForbiddenHTTPError) Error

func (e *ForbiddenHTTPError) Error() string

Error makes it compatible with `error` interface.

func (*ForbiddenHTTPError) GetCode

func (e *ForbiddenHTTPError) GetCode() int

func (*ForbiddenHTTPError) GetInternal

func (e *ForbiddenHTTPError) GetInternal() error

func (*ForbiddenHTTPError) GetMessage

func (e *ForbiddenHTTPError) GetMessage() interface{}

func (*ForbiddenHTTPError) SetCode

func (e *ForbiddenHTTPError) SetCode(code int) error

func (*ForbiddenHTTPError) SetInternal

func (e *ForbiddenHTTPError) SetInternal(internal error) error

func (*ForbiddenHTTPError) SetMessage

func (e *ForbiddenHTTPError) SetMessage(message interface{}) error

type JWTAccessGenerator

type JWTAccessGenerator func(ctx *bolo.RequestContext, u bolo.UserInterface) (accessToken string, expiresIn int64, err error)

JWTAccessGenerator emite o access token no formato JWT. Registrado pelo subplugin oauth2_jwt no evento configuration (dono do segredo e dos claims); este pacote permanece agnóstico aos detalhes criptográficos.

type Oauth2PasswordPlugin

type Oauth2PasswordPlugin struct {
	bolo.Pluginer

	Name string
}

func NewPlugin

func NewPlugin(cfg *PluginCfgs) *Oauth2PasswordPlugin

func (*Oauth2PasswordPlugin) BindRoutes

func (r *Oauth2PasswordPlugin) BindRoutes(app bolo.App) error

func (*Oauth2PasswordPlugin) GetMigrations

func (r *Oauth2PasswordPlugin) GetMigrations() []*bolo.Migration

func (*Oauth2PasswordPlugin) GetName

func (p *Oauth2PasswordPlugin) GetName() string

func (*Oauth2PasswordPlugin) Init

func (p *Oauth2PasswordPlugin) Init(app bolo.App) error

type Oauth2TokenData

type Oauth2TokenData struct {
	ID           string      `json:"id"`
	OwnerId      json.Number `json:"ownerId"`
	AccessToken  string      `json:"access_token"`
	RefreshToken string      `json:"refresh_token"`
	TokenType    string      `json:"token_type"`
	Scopes       []string    `json:"scopes"`
	ExpireDate   time.Time   `json:"expireDate"`
	ExpiresIn    int64       `json:"expiresIn"`
}

func Oauth2GenerateAndSaveToken

func Oauth2GenerateAndSaveToken(ctx *bolo.RequestContext, user bolo.UserInterface) (Oauth2TokenData, error)

func Oauth2GenerateAndSaveTokenJWT

func Oauth2GenerateAndSaveTokenJWT(reqCtx *bolo.RequestContext, user bolo.UserInterface) (Oauth2TokenData, error)

Oauth2GenerateAndSaveTokenJWT emite o par no formato JWT: access token stateless (validação criptográfica, NÃO gravado no Redis) + refresh token opaco rotativo já no formato novo (RT: com família e teto absoluto desde o login — a primeira rotação apenas continua a família).

Exige o gerador JWT registrado via SetJWTAccessGenerator (feito pelo subplugin oauth2_jwt no evento configuration); sem ele devolve erro explícito em vez de emitir token em formato desconhecido.

func Oauth2GenerateToken

func Oauth2GenerateToken(ctx *bolo.RequestContext, u bolo.UserInterface) (Oauth2TokenData, error)

func (*Oauth2TokenData) IsValid

func (r *Oauth2TokenData) IsValid() bool

type PluginCfgs

type PluginCfgs struct{}

type RefreshErrKind

type RefreshErrKind int

RefreshErrKind classifica a falha do fluxo de refresh para o chamador mapear em status/mensagens HTTP sem conhecer detalhes de Redis.

const (
	// ErrInternal - erro de infra (Redis fora, dados corrompidos, config
	// inválida): o chamador deve responder 500 e NADA é revogado.
	ErrInternal RefreshErrKind = iota
	// ErrExpiredIdle - token nunca usado e vencido pelo tempo de inatividade.
	ErrExpiredIdle
	// ErrExpiredAbsolute - família venceu o teto absoluto desde o primeiro token.
	ErrExpiredAbsolute
	// ErrReuseDetected - token já usado reapresentado fora da graça: possível
	// roubo, a família inteira é revogada.
	ErrReuseDetected
	// ErrUserInvalid - dono do token não existe mais, está inativo ou bloqueado.
	ErrUserInvalid
)

func (RefreshErrKind) String

func (k RefreshErrKind) String() string

String devolve um rótulo curto do tipo de erro para logs e respostas.

type RefreshError

type RefreshError struct {
	Kind RefreshErrKind
	Err  error
}

RefreshError é o erro retornado pelas funções de refresh/revogação. Err carrega a causa original quando existe (erros de infra).

func (*RefreshError) Error

func (e *RefreshError) Error() string

Error torna o tipo compatível com a interface `error`.

func (*RefreshError) Unwrap

func (e *RefreshError) Unwrap() error

Unwrap expõe a causa original para errors.Is/As.

type RefreshTokenRecord

type RefreshTokenRecord struct {
	OwnerID  json.Number `json:"ownerId"`
	FamilyID string      `json:"familyId"` // uuid da família de tokens
	// CreatedAt é diagnóstico/forense (quando cada token da família foi
	// emitido); nenhum fluxo decide por ele.
	CreatedAt        time.Time `json:"createdAt"`
	AbsoluteDeadline time.Time `json:"absoluteDeadline"`
}

RefreshTokenRecord é o conteúdo persistido em RT:<refresh token>. É o formato novo, com família e teto absoluto.

type RevokeTokenRequestBody

type RevokeTokenRequestBody struct {
	Token         string `json:"token" validate:"required"`
	TokenTypeHint string `json:"token_type_hint"`
}

RevokeTokenRequestBody corpo do endpoint de revogação (RFC 7009).

type TokenPair

type TokenPair struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
	ExpiresIn    int64  `json:"expires_in"`
}

TokenPair é o par oauth2 devolvido nas rotações (mesmo formato do corpo da resposta do grant password).

type UnauthorizedHTTPError

type UnauthorizedHTTPError struct {
	ForbiddenHTTPError
}

UnauthorizedHTTPError é o erro do modo strict 401: token Bearer inexistente/expirado responde 401 com header WWW-Authenticate (RFC 6750) e corpo no formato BaseErrorResponse. Embute ForbiddenHTTPError para não duplicar o contrato de corpo/JSON (mesma forma serializada; apenas a semântica do código difere — 401 via factory abaixo).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL