♻️ Refactored token verification

This commit is contained in:
Maxim Lebedev 2021-09-20 23:25:08 +05:00
parent 451da06bc6
commit 53098497a5
Signed by: toby3d
GPG Key ID: 1F14E25B7C119FC5
5 changed files with 113 additions and 6 deletions

View File

@ -1,6 +1,7 @@
package http package http
import ( import (
"bytes"
"strings" "strings"
"github.com/fasthttp/router" "github.com/fasthttp/router"
@ -22,6 +23,12 @@ type (
Token string Token string
} }
VerificationResponse struct {
Me string `json:"me"`
ClientID string `json:"client_id"`
Scope string `json:"scope"`
}
RevocationResponse struct{} RevocationResponse struct{}
) )
@ -37,12 +44,42 @@ func NewRequestHandler(useCase token.UseCase) *RequestHandler {
} }
func (h *RequestHandler) Register(r *router.Router) { func (h *RequestHandler) Register(r *router.Router) {
r.GET("/token", h.Read)
r.POST("/token", h.Update) r.POST("/token", h.Update)
} }
func (h *RequestHandler) Update(ctx *http.RequestCtx) { func (h *RequestHandler) Read(ctx *http.RequestCtx) {
ctx.SetStatusCode(http.StatusBadRequest) ctx.SetContentType(common.MIMEApplicationJSON)
ctx.SetStatusCode(http.StatusOK)
encoder := json.NewEncoder(ctx)
rawToken := ctx.Request.Header.Peek(http.HeaderAuthorization)
token, err := h.useCase.Verify(ctx, string(bytes.TrimSpace(bytes.TrimPrefix(rawToken, []byte("Bearer")))))
if err != nil {
ctx.SetStatusCode(http.StatusBadRequest)
encoder.Encode(err)
return
}
if token == nil {
ctx.SetStatusCode(http.StatusUnauthorized)
return
}
if err := encoder.Encode(&VerificationResponse{
Me: token.Me,
ClientID: token.ClientID,
Scope: strings.Join(token.Scopes, " "),
}); err != nil {
ctx.SetStatusCode(http.StatusInternalServerError)
encoder.Encode(err)
}
}
func (h *RequestHandler) Update(ctx *http.RequestCtx) {
switch string(ctx.FormValue(Action)) { switch string(ctx.FormValue(Action)) {
case ActionRevoke: case ActionRevoke:
h.Revocation(ctx) h.Revocation(ctx)
@ -70,11 +107,9 @@ func (h *RequestHandler) Revocation(ctx *http.RequestCtx) {
return return
} }
if err := encoder.Encode(RevocationResponse{}); err != nil { if err := encoder.Encode(&RevocationResponse{}); err != nil {
ctx.SetStatusCode(http.StatusInternalServerError) ctx.SetStatusCode(http.StatusInternalServerError)
encoder.Encode(err) encoder.Encode(err)
return
} }
} }

View File

@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/brianvoe/gofakeit" "github.com/brianvoe/gofakeit"
"github.com/goccy/go-json"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
http "github.com/valyala/fasthttp" http "github.com/valyala/fasthttp"
@ -18,7 +19,48 @@ import (
"source.toby3d.me/website/oauth/internal/util" "source.toby3d.me/website/oauth/internal/util"
) )
func TestVerification(t *testing.T) {
t.Parallel()
require := require.New(t)
assert := assert.New(t)
repo := repository.NewMemoryTokenRepository()
accessToken := model.Token{
AccessToken: gofakeit.Password(true, true, true, true, false, 32),
Type: "Bearer",
ClientID: "https://app.example.com/",
Me: "https://user.example.net/",
Scopes: []string{"create", "update", "delete"},
}
require.NoError(repo.Create(context.TODO(), &accessToken))
req := http.AcquireRequest()
defer http.ReleaseRequest(req)
req.Header.SetMethod(http.MethodGet)
req.SetRequestURI("http://localhost/token")
req.Header.Set(http.HeaderAccept, common.MIMEApplicationJSON)
req.Header.Set(http.HeaderAuthorization, "Bearer "+accessToken.AccessToken)
resp := http.AcquireResponse()
defer http.ReleaseResponse(resp)
require.NoError(util.Serve(delivery.NewRequestHandler(usecase.NewTokenUseCase(repo)).Read, req, resp))
assert.Equal(http.StatusOK, resp.StatusCode())
token := new(delivery.VerificationResponse)
require.NoError(json.Unmarshal(resp.Body(), token))
assert.Equal(&delivery.VerificationResponse{
Me: accessToken.Me,
ClientID: accessToken.ClientID,
Scope: strings.Join(accessToken.Scopes, " "),
}, token)
}
func TestRevocation(t *testing.T) { func TestRevocation(t *testing.T) {
t.Parallel()
require := require.New(t) require := require.New(t)
assert := assert.New(t) assert := assert.New(t)
repo := repository.NewMemoryTokenRepository() repo := repository.NewMemoryTokenRepository()
@ -45,7 +87,7 @@ func TestRevocation(t *testing.T) {
resp := http.AcquireResponse() resp := http.AcquireResponse()
defer http.ReleaseResponse(resp) defer http.ReleaseResponse(resp)
require.NoError(util.Serve(delivery.NewRequestHandler(usecase.NewTokenUseCase(repo)).Revocation, req, resp)) require.NoError(util.Serve(delivery.NewRequestHandler(usecase.NewTokenUseCase(repo)).Update, req, resp))
assert.Equal(http.StatusOK, resp.StatusCode()) assert.Equal(http.StatusOK, resp.StatusCode())
assert.Equal(`{}`, strings.TrimSpace(string(resp.Body()))) assert.Equal(`{}`, strings.TrimSpace(string(resp.Body())))

View File

@ -2,8 +2,11 @@ package token
import ( import (
"context" "context"
"source.toby3d.me/website/oauth/internal/model"
) )
type UseCase interface { type UseCase interface {
Verify(ctx context.Context, token string) (*model.Token, error)
Revoke(ctx context.Context, token string) error Revoke(ctx context.Context, token string) error
} }

View File

@ -3,6 +3,7 @@ package usecase
import ( import (
"context" "context"
"source.toby3d.me/website/oauth/internal/model"
"source.toby3d.me/website/oauth/internal/token" "source.toby3d.me/website/oauth/internal/token"
) )
@ -16,6 +17,10 @@ func NewTokenUseCase(tokens token.Repository) token.UseCase {
} }
} }
func (useCase *tokenUseCase) Verify(ctx context.Context, token string) (*model.Token, error) {
return useCase.tokens.Get(ctx, token)
}
func (useCase *tokenUseCase) Revoke(ctx context.Context, token string) error { func (useCase *tokenUseCase) Revoke(ctx context.Context, token string) error {
return useCase.tokens.Delete(ctx, token) return useCase.tokens.Delete(ctx, token)
} }

View File

@ -13,6 +13,28 @@ import (
"source.toby3d.me/website/oauth/internal/token/usecase" "source.toby3d.me/website/oauth/internal/token/usecase"
) )
func TestVerify(t *testing.T) {
t.Parallel()
require := require.New(t)
assert := assert.New(t)
repo := repository.NewMemoryTokenRepository()
ucase := usecase.NewTokenUseCase(repo)
accessToken := &model.Token{
AccessToken: gofakeit.Password(true, true, true, true, false, 32),
Type: "Bearer",
ClientID: "https://app.example.com/",
Me: "https://user.example.net/",
Scopes: []string{"create", "update", "delete"},
}
require.NoError(repo.Create(context.TODO(), accessToken))
token, err := ucase.Verify(context.TODO(), accessToken.AccessToken)
require.NoError(err)
assert.Equal(accessToken, token)
}
func TestRevoke(t *testing.T) { func TestRevoke(t *testing.T) {
t.Parallel() t.Parallel()