144 lines
3.4 KiB
Go
144 lines
3.4 KiB
Go
package bolt
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
json "github.com/goccy/go-json"
|
|
"github.com/pkg/errors"
|
|
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:"accessToken"`
|
|
ClientID string `json:"clientId"`
|
|
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 *bolt.Tx) error {
|
|
//nolint: exhaustivestruct
|
|
_, err := tx.CreateBucketIfNotExists(Token{}.Bucket())
|
|
|
|
return errors.Wrap(err, "failed to create a bucket")
|
|
}); err != nil {
|
|
return nil, errors.Wrap(err, "failed to update the storage structure")
|
|
}
|
|
|
|
return &boltTokenRepository{
|
|
db: db,
|
|
}, nil
|
|
}
|
|
|
|
func (repo *boltTokenRepository) Get(ctx context.Context, accessToken string) (*model.Token, error) {
|
|
result := model.NewToken()
|
|
|
|
if err := repo.db.View(func(tx *bolt.Tx) error {
|
|
//nolint: exhaustivestruct
|
|
if src := tx.Bucket(Token{}.Bucket()).Get([]byte(accessToken)); src != nil {
|
|
return NewToken().Bind(src, result)
|
|
}
|
|
|
|
return ErrNotExist
|
|
}); err != nil {
|
|
if !xerrors.Is(err, ErrNotExist) {
|
|
return nil, errors.Wrap(err, "failed to retrieve token from storage")
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
if err = repo.db.Update(func(tx *bolt.Tx) error {
|
|
if err := tx.Bucket(dto.Bucket()).Put([]byte(dto.AccessToken), src); err != nil {
|
|
return errors.Wrap(err, "failed to overwrite the token in the bucket")
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return errors.Wrap(err, "failed to update the token in the repository")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (repo *boltTokenRepository) Remove(ctx context.Context, accessToken string) error {
|
|
if err := repo.db.Update(func(tx *bolt.Tx) error {
|
|
//nolint: exhaustivestruct
|
|
if err := tx.Bucket(Token{}.Bucket()).Delete([]byte(accessToken)); err != nil {
|
|
return errors.Wrap(err, "failed to remove token in bucket")
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return errors.Wrap(err, "failed to remove token from storage")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
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 = string(src.ClientID)
|
|
t.Me = string(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 errors.Wrap(err, "cannot unmarshal token source")
|
|
}
|
|
|
|
dst.AccessToken = t.AccessToken
|
|
dst.Scopes = strings.Fields(t.Scope)
|
|
dst.Type = t.Type
|
|
dst.ClientID = model.URL(t.ClientID)
|
|
dst.Me = model.URL(t.Me)
|
|
|
|
return nil
|
|
}
|