🎨 Format token package usage

This commit is contained in:
Maxim Lebedev 2021-09-23 02:32:40 +05:00
parent 53098497a5
commit 22187f309b
Signed by: toby3d
GPG Key ID: 1F14E25B7C119FC5
6 changed files with 205 additions and 92 deletions

View File

@ -6,8 +6,6 @@ import (
"golang.org/x/xerrors"
)
// TODO(toby3d): make more informative errors.
// See https://indieauth.spec.indieweb.org/#authorization-request
type Error struct {
Code string `json:"error"`
Description string `json:"error_description,omitempty"`
@ -15,48 +13,43 @@ type Error struct {
Frame xerrors.Frame `json:"-"`
}
var (
ErrInvalidRequest Error = Error{
Code: "invalid_request",
Description: "the request is missing a required parameter, includes an invalid parameter value, or is otherwise malformed",
}
ErrUnauthorizedClient Error = Error{
Code: "unauthorized_client",
Description: "the client is not authorized to request an authorization code using this method",
}
ErrAccessDenied Error = Error{
Code: "access_denied",
Description: "",
}
ErrUnsupportedResponseType Error = Error{
Code: "unsupported_response_type",
Description: "the authorization server does not support obtaining an authorization code using this method",
}
ErrInvalidScope Error = Error{
Code: "invalid_scope",
Description: "the requested scope is invalid, unknown, or malformed",
}
ErrServerError Error = Error{
Code: "server_error",
Description: "the authorization server encountered an unexpected condition which prevented it from fulfilling the request",
}
ErrTemporarilyUnavailable Error = Error{
Code: "temporarily_unavailable",
Description: "the authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server",
}
const (
ErrAccessDenied string = "access_denied"
ErrInvalidClient string = "invalid_client"
ErrInvalidGrant string = "invalid_grant"
ErrInvalidRequest string = "invalid_request"
ErrInvalidScope string = "invalid_scope"
ErrInvalidToken string = "invalid_token"
ErrServerError string = "server_error"
ErrTemporarilyUnavailable string = "temporarily_unavailable"
ErrUnauthorizedClient string = "unauthorized_client"
ErrUnsupportedResponseType string = "unsupported_response_type"
)
func (e Error) FormatError(p xerrors.Printer) error {
p.Printf("%s: %s", e.Code, e.Description)
e.Frame.Format(p)
const errorColor string = "\033[31m"
return nil
func (e Error) Error() string {
return fmt.Sprint(e)
}
func (e Error) Format(s fmt.State, r rune) {
xerrors.FormatError(e, s, r)
}
func (e Error) Error() string {
return fmt.Sprint(e)
func (e Error) FormatError(p xerrors.Printer) error {
p.Print(errorColor, e.Code)
if e.Description != "" {
p.Printf(": %s", e.Description)
}
if e.URI != "" {
p.Printf("%4s", e.URI)
}
if p.Detail() {
e.Frame.Format(p)
}
return nil
}

View File

@ -3,11 +3,19 @@ package token
import (
"context"
"golang.org/x/xerrors"
"source.toby3d.me/website/oauth/internal/model"
)
type Repository interface {
Create(ctx context.Context, token *model.Token) error
Get(ctx context.Context, token string) (*model.Token, error)
Delete(ctx context.Context, token string) error
Get(ctx context.Context, accessToken string) (*model.Token, error)
Create(ctx context.Context, accessToken *model.Token) error
Update(ctx context.Context, accessToken *model.Token) error
Remove(ctx context.Context, accessToken string) error
}
var ErrExist error = model.Error{
Code: model.ErrInvalidRequest,
Description: "this token is already exists",
Frame: xerrors.Caller(1),
}

View File

@ -9,67 +9,50 @@ import (
)
type memoryTokenRepository struct {
mutex *sync.RWMutex
tokens []*model.Token
tokens *sync.Map
}
func NewMemoryTokenRepository() token.Repository {
func NewMemoryTokenRepository(tokens *sync.Map) token.Repository {
return &memoryTokenRepository{
mutex: new(sync.RWMutex),
tokens: make([]*model.Token, 0),
tokens: tokens,
}
}
func (repo *memoryTokenRepository) Create(ctx context.Context, token *model.Token) error {
repo.mutex.Lock()
func (repo *memoryTokenRepository) Get(ctx context.Context, accessToken string) (*model.Token, error) {
src, ok := repo.tokens.Load(accessToken)
if !ok {
return nil, nil
}
repo.tokens = append(repo.tokens, token)
result, ok := src.(*model.Token)
if !ok {
return nil, nil
}
repo.mutex.Unlock()
return result, nil
}
func (repo *memoryTokenRepository) Create(ctx context.Context, accessToken *model.Token) error {
t, err := repo.Get(ctx, accessToken.AccessToken)
if err != nil {
return err
}
if t != nil {
return token.ErrExist
}
return repo.Update(ctx, accessToken)
}
func (repo *memoryTokenRepository) Update(ctx context.Context, accessToken *model.Token) error {
repo.tokens.Store(accessToken.AccessToken, accessToken)
return nil
}
func (repo *memoryTokenRepository) Get(ctx context.Context, token string) (*model.Token, error) {
repo.mutex.RLock()
defer repo.mutex.RUnlock()
for i := range repo.tokens {
if repo.tokens[i].AccessToken != token {
continue
}
return repo.tokens[i], nil
}
return nil, nil
}
func (repo *memoryTokenRepository) Delete(ctx context.Context, token string) error {
repo.mutex.RLock()
for i := range repo.tokens {
if repo.tokens[i].AccessToken != token {
continue
}
repo.mutex.RUnlock()
repo.mutex.Lock()
if i < len(repo.tokens)-1 {
copy(repo.tokens[i:], repo.tokens[i+1:])
}
repo.tokens[len(repo.tokens)-1] = nil
repo.tokens = repo.tokens[:len(repo.tokens)-1]
repo.mutex.Unlock()
repo.mutex.RLock()
break
}
repo.mutex.RUnlock()
func (repo *memoryTokenRepository) Remove(ctx context.Context, accessToken string) error {
repo.tokens.Delete(accessToken)
return nil
}

View File

@ -0,0 +1,126 @@
package memory_test
import (
"context"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"source.toby3d.me/website/oauth/internal/model"
"source.toby3d.me/website/oauth/internal/random"
"source.toby3d.me/website/oauth/internal/token"
"source.toby3d.me/website/oauth/internal/token/repository/memory"
)
func TestGet(t *testing.T) {
t.Parallel()
store := new(sync.Map)
accessToken := &model.Token{
AccessToken: random.New().String(32),
ClientID: "https://app.example.com/",
Me: "https://toby3d.me/",
Profile: &model.Profile{
Name: "Maxim Lebedev",
URL: "https://toby3d.me/",
Photo: "https://toby3d.me/photo.jpg",
Email: "hey@toby3d.me",
},
Scopes: []string{"read", "update", "delete"},
Type: "Bearer",
}
store.Store(accessToken.AccessToken, accessToken)
result, err := memory.NewMemoryTokenRepository(store).Get(context.TODO(), accessToken.AccessToken)
require.NoError(t, err)
assert.Equal(t, accessToken, result)
}
func TestCreate(t *testing.T) {
t.Parallel()
store := new(sync.Map)
accessToken := &model.Token{
AccessToken: random.New().String(32),
ClientID: "https://app.example.com/",
Me: "https://toby3d.me/",
Profile: &model.Profile{
Name: "Maxim Lebedev",
URL: "https://toby3d.me/",
Photo: "https://toby3d.me/photo.jpg",
Email: "hey@toby3d.me",
},
Scopes: []string{"read", "update", "delete"},
Type: "Bearer",
}
repo := memory.NewMemoryTokenRepository(store)
require.NoError(t, repo.Create(context.TODO(), accessToken))
result, ok := store.Load(accessToken.AccessToken)
assert.True(t, ok)
assert.Equal(t, accessToken, result)
assert.EqualError(t, repo.Create(context.TODO(), accessToken), token.ErrExist.Error())
}
func TestUpdate(t *testing.T) {
t.Parallel()
store := new(sync.Map)
accessToken := &model.Token{
AccessToken: random.New().String(32),
ClientID: "https://app.example.com/",
Me: "https://toby3d.me/",
Profile: &model.Profile{
Name: "Maxim Lebedev",
URL: "https://toby3d.me/",
Photo: "https://toby3d.me/photo.jpg",
Email: "hey@toby3d.me",
},
Scopes: []string{"read", "update", "delete"},
Type: "Bearer",
}
store.Store(accessToken.AccessToken, accessToken)
tokenCopy := *accessToken
tokenCopy.ClientID = "https://client.example.com/"
tokenCopy.Me = "https://toby3d.ru/"
require.NoError(t, memory.NewMemoryTokenRepository(store).Update(context.TODO(), &tokenCopy))
result, ok := store.Load(accessToken.AccessToken)
assert.True(t, ok)
assert.NotEqual(t, accessToken, result)
assert.Equal(t, &tokenCopy, result)
}
func TestDelete(t *testing.T) {
t.Parallel()
store := new(sync.Map)
accessToken := &model.Token{
AccessToken: random.New().String(32),
ClientID: "https://app.example.com/",
Me: "https://toby3d.me/",
Profile: &model.Profile{
Name: "Maxim Lebedev",
URL: "https://toby3d.me/",
Photo: "https://toby3d.me/photo.jpg",
Email: "hey@toby3d.me",
},
Scopes: []string{"read", "update", "delete"},
Type: "Bearer",
}
store.Store(accessToken.AccessToken, accessToken)
require.NoError(t, memory.NewMemoryTokenRepository(store).Remove(context.TODO(), accessToken.AccessToken))
result, ok := store.Load(accessToken.AccessToken)
assert.False(t, ok)
assert.Nil(t, result)
}

View File

@ -22,5 +22,5 @@ func (useCase *tokenUseCase) Verify(ctx context.Context, token string) (*model.T
}
func (useCase *tokenUseCase) Revoke(ctx context.Context, token string) error {
return useCase.tokens.Delete(ctx, token)
return useCase.tokens.Remove(ctx, token)
}

View File

@ -2,6 +2,7 @@ package usecase_test
import (
"context"
"sync"
"testing"
"github.com/brianvoe/gofakeit"
@ -18,7 +19,8 @@ func TestVerify(t *testing.T) {
require := require.New(t)
assert := assert.New(t)
repo := repository.NewMemoryTokenRepository()
store := new(sync.Map)
repo := repository.NewMemoryTokenRepository(store)
ucase := usecase.NewTokenUseCase(repo)
accessToken := &model.Token{
AccessToken: gofakeit.Password(true, true, true, true, false, 32),
@ -40,7 +42,8 @@ func TestRevoke(t *testing.T) {
require := require.New(t)
assert := assert.New(t)
repo := repository.NewMemoryTokenRepository()
store := new(sync.Map)
repo := repository.NewMemoryTokenRepository(store)
ucase := usecase.NewTokenUseCase(repo)
accessToken := gofakeit.Password(true, true, true, true, false, 32)