🗃️ Created bbolt repository for tokens
This commit is contained in:
parent
22187f309b
commit
fafb4bbc94
|
@ -0,0 +1,125 @@
|
|||
package bolt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
json "github.com/goccy/go-json"
|
||||
"github.com/pkg/errors"
|
||||
"go.etcd.io/bbolt"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"golang.org/x/xerrors"
|
||||
"source.toby3d.me/website/oauth/internal/model"
|
||||
"source.toby3d.me/website/oauth/internal/token"
|
||||
)
|
||||
|
||||
type (
|
||||
Token struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ClientID string `json:"client_id"`
|
||||
Me string `json:"me"`
|
||||
Scope string `json:"scope"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
boltTokenRepository struct {
|
||||
db *bolt.DB
|
||||
}
|
||||
)
|
||||
|
||||
var ErrNotExist error = xerrors.New("key not exist")
|
||||
|
||||
func NewBoltTokenRepository(db *bolt.DB) (token.Repository, error) {
|
||||
if err := db.Update(func(tx *bbolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists(Token{}.Bucket())
|
||||
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &boltTokenRepository{
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (repo *boltTokenRepository) Get(ctx context.Context, accessToken string) (*model.Token, error) {
|
||||
result := new(model.Token)
|
||||
err := repo.db.View(func(tx *bolt.Tx) error {
|
||||
if src := tx.Bucket(Token{}.Bucket()).Get([]byte(accessToken)); src != nil {
|
||||
return NewToken().Bind(src, result)
|
||||
}
|
||||
|
||||
return ErrNotExist
|
||||
})
|
||||
if err != nil && !xerrors.Is(err, ErrNotExist) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if xerrors.Is(err, ErrNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (repo *boltTokenRepository) Create(ctx context.Context, accessToken *model.Token) error {
|
||||
t, err := repo.Get(ctx, accessToken.AccessToken)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to verify the existence of the token")
|
||||
}
|
||||
|
||||
if t != nil {
|
||||
return token.ErrExist
|
||||
}
|
||||
|
||||
return repo.Update(ctx, accessToken)
|
||||
}
|
||||
|
||||
func (repo *boltTokenRepository) Update(ctx context.Context, accessToken *model.Token) error {
|
||||
dto := NewToken()
|
||||
dto.Populate(accessToken)
|
||||
|
||||
src, err := json.Marshal(dto)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal token")
|
||||
}
|
||||
|
||||
return repo.db.Update(func(tx *bolt.Tx) error {
|
||||
return tx.Bucket(dto.Bucket()).Put([]byte(dto.AccessToken), src)
|
||||
})
|
||||
}
|
||||
|
||||
func (repo *boltTokenRepository) Remove(ctx context.Context, accessToken string) error {
|
||||
return repo.db.Update(func(tx *bolt.Tx) error {
|
||||
return tx.Bucket(Token{}.Bucket()).Delete([]byte(accessToken))
|
||||
})
|
||||
}
|
||||
|
||||
func NewToken() *Token {
|
||||
return new(Token)
|
||||
}
|
||||
|
||||
func (Token) Bucket() []byte { return []byte("tokens") }
|
||||
|
||||
func (t *Token) Populate(src *model.Token) {
|
||||
t.AccessToken = src.AccessToken
|
||||
t.ClientID = src.ClientID
|
||||
t.Me = src.Me
|
||||
t.Scope = strings.Join(src.Scopes, " ")
|
||||
t.Type = src.Type
|
||||
}
|
||||
|
||||
func (t *Token) Bind(src []byte, dst *model.Token) error {
|
||||
if err := json.Unmarshal(src, t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dst.AccessToken = t.AccessToken
|
||||
dst.ClientID = t.ClientID
|
||||
dst.Me = t.Me
|
||||
dst.Scopes = strings.Fields(t.Scope)
|
||||
dst.Type = t.Type
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
package bolt_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
json "github.com/goccy/go-json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.etcd.io/bbolt"
|
||||
"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/bolt"
|
||||
)
|
||||
|
||||
var (
|
||||
db *bbolt.DB
|
||||
repo token.Repository
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var err error
|
||||
|
||||
dbPath := filepath.Join("..", "..", "..", "..", "test", "testing.db")
|
||||
if db, err = bbolt.Open(dbPath, os.ModePerm, nil); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if repo, err = bolt.NewBoltTokenRepository(db); err != nil {
|
||||
_ = db.Close()
|
||||
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
code := m.Run()
|
||||
_ = db.Close()
|
||||
_ = os.RemoveAll(dbPath)
|
||||
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
accessToken := random.New().String(32)
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = db.Update(func(tx *bbolt.Tx) error {
|
||||
return tx.Bucket(bolt.Token{}.Bucket()).Delete([]byte(accessToken))
|
||||
})
|
||||
})
|
||||
|
||||
src, err := json.Marshal(&bolt.Token{
|
||||
AccessToken: accessToken,
|
||||
ClientID: "https://app.example.com/",
|
||||
Me: "https://toby3d.me/",
|
||||
Scope: "read update delete",
|
||||
Type: "Bearer",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, db.Update(func(tx *bbolt.Tx) error {
|
||||
return tx.Bucket(bolt.Token{}.Bucket()).Put([]byte(accessToken), src)
|
||||
}))
|
||||
|
||||
tkn, err := repo.Get(context.TODO(), accessToken)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &model.Token{
|
||||
AccessToken: accessToken,
|
||||
ClientID: "https://app.example.com/",
|
||||
Me: "https://toby3d.me/",
|
||||
Scopes: []string{"read", "update", "delete"},
|
||||
Type: "Bearer",
|
||||
}, tkn)
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
accessToken := &model.Token{
|
||||
AccessToken: random.New().String(32),
|
||||
ClientID: "https://app.example.com/",
|
||||
Me: "https://toby3d.me/",
|
||||
Scopes: []string{"read", "update", "delete"},
|
||||
Type: "Bearer",
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = db.Update(func(tx *bbolt.Tx) error {
|
||||
return tx.Bucket(bolt.Token{}.Bucket()).Delete([]byte(accessToken.AccessToken))
|
||||
})
|
||||
})
|
||||
|
||||
require.NoError(t, repo.Create(context.TODO(), accessToken))
|
||||
|
||||
result := new(model.Token)
|
||||
require.NoError(t, db.View(func(tx *bbolt.Tx) error {
|
||||
return bolt.NewToken().Bind(tx.Bucket(bolt.Token{}.Bucket()).Get([]byte(accessToken.AccessToken)), result)
|
||||
}))
|
||||
assert.Equal(t, accessToken, result)
|
||||
|
||||
assert.EqualError(t, repo.Create(context.TODO(), accessToken), token.ErrExist.Error())
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
accessToken := random.New().String(32)
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = db.Update(func(tx *bbolt.Tx) error {
|
||||
return tx.Bucket(bolt.Token{}.Bucket()).Delete([]byte(accessToken))
|
||||
})
|
||||
})
|
||||
|
||||
src, err := json.Marshal(&bolt.Token{
|
||||
AccessToken: accessToken,
|
||||
ClientID: "https://app.example.com/",
|
||||
Me: "https://toby3d.me/",
|
||||
Scope: "read update delete",
|
||||
Type: "Bearer",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, db.Update(func(tx *bbolt.Tx) error {
|
||||
return tx.Bucket(bolt.Token{}.Bucket()).Put([]byte(accessToken), src)
|
||||
}))
|
||||
|
||||
require.NoError(t, repo.Update(context.TODO(), &model.Token{
|
||||
AccessToken: accessToken,
|
||||
ClientID: "https://client.example.com/",
|
||||
Me: "https://toby3d.ru/",
|
||||
Scopes: []string{"read"},
|
||||
Type: "Bearer",
|
||||
}))
|
||||
|
||||
result := new(model.Token)
|
||||
require.NoError(t, db.View(func(tx *bbolt.Tx) error {
|
||||
return bolt.NewToken().Bind(tx.Bucket(bolt.Token{}.Bucket()).Get([]byte(accessToken)), result)
|
||||
}))
|
||||
assert.Equal(t, &model.Token{
|
||||
AccessToken: accessToken,
|
||||
ClientID: "https://client.example.com/",
|
||||
Me: "https://toby3d.ru/",
|
||||
Scopes: []string{"read"},
|
||||
Type: "Bearer",
|
||||
}, result)
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
accessToken := random.New().String(32)
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = db.Update(func(tx *bbolt.Tx) error {
|
||||
return tx.Bucket(bolt.Token{}.Bucket()).Delete([]byte(accessToken))
|
||||
})
|
||||
})
|
||||
|
||||
src, err := json.Marshal(&bolt.Token{
|
||||
AccessToken: accessToken,
|
||||
ClientID: "https://app.example.com/",
|
||||
Me: "https://toby3d.me/",
|
||||
Scope: "read update delete",
|
||||
Type: "Bearer",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, db.Update(func(tx *bbolt.Tx) error {
|
||||
return tx.Bucket(bolt.Token{}.Bucket()).Put([]byte(accessToken), src)
|
||||
}))
|
||||
|
||||
require.NoError(t, repo.Remove(context.TODO(), accessToken))
|
||||
|
||||
require.NoError(t, db.View(func(tx *bbolt.Tx) error {
|
||||
assert.Nil(t, tx.Bucket(bolt.Token{}.Bucket()).Get([]byte(accessToken)))
|
||||
|
||||
return nil
|
||||
}))
|
||||
}
|
Loading…
Reference in New Issue