♻️ 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
import (
"bytes"
"strings"
"github.com/fasthttp/router"
@ -22,6 +23,12 @@ type (
Token string
}
VerificationResponse struct {
Me string `json:"me"`
ClientID string `json:"client_id"`
Scope string `json:"scope"`
}
RevocationResponse struct{}
)
@ -37,12 +44,42 @@ func NewRequestHandler(useCase token.UseCase) *RequestHandler {
}
func (h *RequestHandler) Register(r *router.Router) {
r.GET("/token", h.Read)
r.POST("/token", h.Update)
}
func (h *RequestHandler) Update(ctx *http.RequestCtx) {
ctx.SetStatusCode(http.StatusBadRequest)
func (h *RequestHandler) Read(ctx *http.RequestCtx) {
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)) {
case ActionRevoke:
h.Revocation(ctx)
@ -70,11 +107,9 @@ func (h *RequestHandler) Revocation(ctx *http.RequestCtx) {
return
}
if err := encoder.Encode(RevocationResponse{}); err != nil {
if err := encoder.Encode(&RevocationResponse{}); err != nil {
ctx.SetStatusCode(http.StatusInternalServerError)
encoder.Encode(err)
return
}
}

View File

@ -6,6 +6,7 @@ import (
"testing"
"github.com/brianvoe/gofakeit"
"github.com/goccy/go-json"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
http "github.com/valyala/fasthttp"
@ -18,7 +19,48 @@ import (
"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) {
t.Parallel()
require := require.New(t)
assert := assert.New(t)
repo := repository.NewMemoryTokenRepository()
@ -45,7 +87,7 @@ func TestRevocation(t *testing.T) {
resp := http.AcquireResponse()
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(`{}`, strings.TrimSpace(string(resp.Body())))

View File

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

View File

@ -3,6 +3,7 @@ package usecase
import (
"context"
"source.toby3d.me/website/oauth/internal/model"
"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 {
return useCase.tokens.Delete(ctx, token)
}

View File

@ -13,6 +13,28 @@ import (
"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) {
t.Parallel()