feat: add CanRefreshTokenInfo to support non-JWT refresh tokens (#244)

* Add an additional, optional, op.Storage interface so that refresh tokens
that are not JWTs do not cause failures when they randomly, sometimes, decrypt
without error

```go
// CanRefreshTokenInfo is an optional additional interface that Storage can support.
// Supporting CanRefreshTokenInfo is required to be able to revoke a refresh token that
// does not happen to also be a JWTs work properly.
type CanRefreshTokenInfo interface {
        // GetRefreshTokenInfo must return oidc.ErrInvalidRefreshToken when presented
	// with a token that is not a refresh token.
	GetRefreshTokenInfo(ctx context.Context, clientID string, token string) (userID string, tokenID string, err error)
}
```

* add comment suggested in code review

* review feedback: return an error defined in op rather than adding a new error to oidc

* move ErrInvalidRefresToken to op/storage.go
This commit is contained in:
David Sharnoff 2023-02-05 23:27:57 -08:00 committed by GitHub
parent fa222c5efb
commit cdf2af6c2c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 37 additions and 4 deletions

View file

@ -2,6 +2,7 @@ package op
import (
"context"
"errors"
"net/http"
"net/url"
"strings"
@ -31,14 +32,33 @@ func revocationHandler(revoker Revoker) func(http.ResponseWriter, *http.Request)
}
func Revoke(w http.ResponseWriter, r *http.Request, revoker Revoker) {
token, _, clientID, err := ParseTokenRevocationRequest(r, revoker)
token, tokenTypeHint, clientID, err := ParseTokenRevocationRequest(r, revoker)
if err != nil {
RevocationRequestError(w, r, err)
return
}
tokenID, subject, ok := getTokenIDAndSubjectForRevocation(r.Context(), revoker, token)
if ok {
token = tokenID
var subject string
doDecrypt := true
if canRefreshInfo, ok := revoker.Storage().(CanRefreshTokenInfo); ok && tokenTypeHint != "access_token" {
userID, tokenID, err := canRefreshInfo.GetRefreshTokenInfo(r.Context(), clientID, token)
if err != nil {
// An invalid refresh token means that we'll try other things (leaving doDecrypt==true)
if !errors.Is(err, ErrInvalidRefreshToken) {
RevocationRequestError(w, r, oidc.ErrServerError().WithParent(err))
return
}
} else {
token = tokenID
subject = userID
doDecrypt = false
}
}
if doDecrypt {
tokenID, userID, ok := getTokenIDAndSubjectForRevocation(r.Context(), revoker, token)
if ok {
token = tokenID
subject = userID
}
}
if err := revoker.Storage().RevokeToken(r.Context(), token, subject, clientID); err != nil {
RevocationRequestError(w, r, err)