🗃️ Created bbolt repository for tokens

This commit is contained in:
Maxim Lebedev 2021-09-23 04:26:13 +05:00
parent 22187f309b
commit fafb4bbc94
Signed by: toby3d
GPG Key ID: 1F14E25B7C119FC5
2 changed files with 310 additions and 0 deletions

View File

@ -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
}

View File

@ -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
}))
}