🎨 Format token package usage
This commit is contained in:
parent
53098497a5
commit
22187f309b
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue