123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- package auth
- import (
- "context"
- "errors"
- "fmt"
- "github.com/google/uuid"
- jsonIterator "github.com/json-iterator/go"
- "github.com/redis/go-redis/v9"
- "github.com/tidwall/buntdb"
- "time"
- )
- var (
- jsonMarshal = jsonIterator.Marshal
- jsonUnmarshal = jsonIterator.Unmarshal
- )
- type TokenStore interface {
- Create(info TokenInfo) error
- RemoveByAccess(access string) error
- RemoveByRefresh(refresh string) error
- GetByAccess(access string) (TokenInfo, error)
- GetByRefresh(refresh string) (TokenInfo, error)
- }
- // NewMemoryTokenStore create a token buntStore instance based on memory
- func NewMemoryTokenStore() (TokenStore, error) {
- return NewFileTokenStore(":memory:")
- }
- // NewFileTokenStore create a token buntStore instance based on file
- func NewFileTokenStore(filename string) (TokenStore, error) {
- db, err := buntdb.Open(filename)
- if err != nil {
- return nil, err
- }
- return &buntStore{db: db}, nil
- }
- // buntStore token storage based on buntdb(https://github.com/tidwall/buntdb)
- type buntStore struct {
- db *buntdb.DB
- }
- func (ts *buntStore) remove(key string) error {
- err := ts.db.Update(func(tx *buntdb.Tx) error {
- _, err := tx.Delete(key)
- return err
- })
- if errors.Is(err, buntdb.ErrNotFound) {
- return nil
- }
- return err
- }
- func (ts *buntStore) getData(key string) (TokenInfo, error) {
- var ti TokenInfo
- err := ts.db.View(func(tx *buntdb.Tx) error {
- jv, err := tx.Get(key)
- if err != nil {
- return err
- }
- var tm Token
- err = jsonUnmarshal([]byte(jv), &tm)
- if err != nil {
- return err
- }
- ti = &tm
- return nil
- })
- if err != nil {
- if err == buntdb.ErrNotFound {
- return nil, nil
- }
- return nil, err
- }
- return ti, nil
- }
- func (ts *buntStore) getBasicID(key string) (string, error) {
- var basicID string
- err := ts.db.View(func(tx *buntdb.Tx) error {
- v, err := tx.Get(key)
- if err != nil {
- return err
- }
- basicID = v
- return nil
- })
- if err != nil {
- if err == buntdb.ErrNotFound {
- return "", nil
- }
- return "", err
- }
- return basicID, nil
- }
- // Create and buntStore the new token information
- func (ts *buntStore) Create(info TokenInfo) error {
- ct := time.Now()
- jv, err := jsonMarshal(info)
- if err != nil {
- return err
- }
- return ts.db.Update(func(tx *buntdb.Tx) error {
- basicID := uuid.Must(uuid.NewRandom()).String()
- aexp := info.GetAccessExpiresIn()
- rexp := aexp
- expires := true
- if refresh := info.GetRefresh(); refresh != "" {
- rexp = info.GetRefreshCreateAt().Add(info.GetRefreshExpiresIn()).Sub(ct)
- if aexp.Seconds() > rexp.Seconds() {
- aexp = rexp
- }
- expires = info.GetRefreshExpiresIn() != 0
- _, _, err = tx.Set(refresh, basicID, &buntdb.SetOptions{Expires: expires, TTL: rexp})
- if err != nil {
- return err
- }
- }
- _, _, err = tx.Set(basicID, string(jv), &buntdb.SetOptions{Expires: expires, TTL: rexp})
- if err != nil {
- return err
- }
- _, _, err = tx.Set(info.GetAccess(), basicID, &buntdb.SetOptions{Expires: expires, TTL: aexp})
- return err
- })
- }
- // RemoveByAccess use the access token to delete the token information
- func (ts *buntStore) RemoveByAccess(access string) error {
- return ts.remove(access)
- }
- // RemoveByRefresh use the refresh token to delete the token information
- func (ts *buntStore) RemoveByRefresh(refresh string) error {
- return ts.remove(refresh)
- }
- // GetByAccess use the access token for token information data
- func (ts *buntStore) GetByAccess(access string) (TokenInfo, error) {
- basicID, err := ts.getBasicID(access)
- if err != nil {
- return nil, err
- }
- return ts.getData(basicID)
- }
- // GetByRefresh use the refresh token for token information data
- func (ts *buntStore) GetByRefresh(refresh string) (TokenInfo, error) {
- basicID, err := ts.getBasicID(refresh)
- if err != nil {
- return nil, err
- }
- return ts.getData(basicID)
- }
- /*------------------------------------------------------------------------------------*/
- // NewRedisStoreWithCli create an instance of a redis store
- func NewRedisStoreWithCli(cli *redis.Client, keyNamespace string) TokenStore {
- store := &redisStore{
- cli: cli,
- ctx: context.TODO(),
- ns: keyNamespace,
- }
- return store
- }
- // TokenStore redis token store
- type redisStore struct {
- cli *redis.Client
- ctx context.Context
- ns string
- }
- func (s *redisStore) wrapperKey(key string) string {
- return fmt.Sprintf("%s%s", s.ns, key)
- }
- func (s *redisStore) checkError(result redis.Cmder) (bool, error) {
- if err := result.Err(); err != nil {
- if err == redis.Nil {
- return true, nil
- }
- return false, err
- }
- return false, nil
- }
- func (s *redisStore) remove(key string) error {
- result := s.cli.Del(s.ctx, s.wrapperKey(key))
- _, err := s.checkError(result)
- return err
- }
- func (s *redisStore) removeToken(tokenString string, isRefresh bool) error {
- basicID, err := s.getBasicID(tokenString)
- if err != nil {
- return err
- } else if basicID == "" {
- return nil
- }
- err = s.remove(tokenString)
- if err != nil {
- return err
- }
- token, err := s.getToken(basicID)
- if err != nil {
- return err
- } else if token == nil {
- return nil
- }
- checkToken := token.GetRefresh()
- if isRefresh {
- checkToken = token.GetAccess()
- }
- result := s.cli.Exists(s.ctx, s.wrapperKey(checkToken))
- if err = result.Err(); err != nil && err != redis.Nil {
- return err
- } else if result.Val() == 0 {
- return s.remove(basicID)
- }
- return nil
- }
- func (s *redisStore) parseToken(result *redis.StringCmd) (TokenInfo, error) {
- if ok, err := s.checkError(result); err != nil {
- return nil, err
- } else if ok {
- return nil, nil
- }
- buf, err := result.Bytes()
- if err != nil {
- if err == redis.Nil {
- return nil, nil
- }
- return nil, err
- }
- var token Token
- if err = jsonUnmarshal(buf, &token); err != nil {
- return nil, err
- }
- return &token, nil
- }
- func (s *redisStore) getToken(key string) (TokenInfo, error) {
- result := s.cli.Get(s.ctx, s.wrapperKey(key))
- return s.parseToken(result)
- }
- func (s *redisStore) parseBasicID(result *redis.StringCmd) (string, error) {
- if ok, err := s.checkError(result); err != nil {
- return "", err
- } else if ok {
- return "", nil
- }
- return result.Val(), nil
- }
- func (s *redisStore) getBasicID(token string) (string, error) {
- result := s.cli.Get(s.ctx, s.wrapperKey(token))
- return s.parseBasicID(result)
- }
- // Create and store the new token information
- func (s *redisStore) Create(info TokenInfo) error {
- ct := time.Now()
- jv, err := jsonMarshal(info)
- if err != nil {
- return err
- }
- pipe := s.cli.TxPipeline()
- basicID := uuid.Must(uuid.NewRandom()).String()
- aexp := info.GetAccessExpiresIn()
- rexp := aexp
- if refresh := info.GetRefresh(); refresh != "" {
- rexp = info.GetRefreshCreateAt().Add(info.GetRefreshExpiresIn()).Sub(ct)
- if aexp.Seconds() > rexp.Seconds() {
- aexp = rexp
- }
- pipe.Set(s.ctx, s.wrapperKey(refresh), basicID, rexp)
- }
- pipe.Set(s.ctx, s.wrapperKey(info.GetAccess()), basicID, aexp)
- pipe.Set(s.ctx, s.wrapperKey(basicID), jv, rexp)
- if _, err = pipe.Exec(s.ctx); err != nil {
- return err
- }
- return nil
- }
- // RemoveByAccess Use the access token to delete the token information
- func (s *redisStore) RemoveByAccess(access string) error {
- return s.removeToken(access, false)
- }
- // RemoveByRefresh Use the refresh token to delete the token information
- func (s *redisStore) RemoveByRefresh(refresh string) error {
- return s.removeToken(refresh, true)
- }
- // GetByAccess Use the access token for token information data
- func (s *redisStore) GetByAccess(access string) (TokenInfo, error) {
- basicID, err := s.getBasicID(access)
- if err != nil || basicID == "" {
- return nil, err
- }
- return s.getToken(basicID)
- }
- // GetByRefresh Use the refresh token for token information data
- func (s *redisStore) GetByRefresh(refresh string) (TokenInfo, error) {
- basicID, err := s.getBasicID(refresh)
- if err != nil || basicID == "" {
- return nil, err
- }
- return s.getToken(basicID)
- }
|