filetokencache.go raw

   1  package authenticator
   2  
   3  import (
   4  	"encoding/json"
   5  	"fmt"
   6  	"io"
   7  	"os"
   8  	"sync"
   9  
  10  	"github.com/transip/gotransip/v6/jwt"
  11  )
  12  
  13  // cacheItem is one named item inside the filesystem cache
  14  type cacheItem struct {
  15  	// Key of the cache item, containing
  16  	Key string `json:"key"`
  17  	// Data containing the content of the cache item
  18  	Data []byte `json:"data"`
  19  }
  20  
  21  // FileTokenCache is a cache that takes a path and writes a json marshalled File to it,
  22  // it decodes it when created with the NewFileTokenCache method.
  23  // It has a Set method to save a token by name as jwt.Token
  24  // and a Get method one to get a previously acquired token by name returned as jwt.Token
  25  type FileTokenCache struct {
  26  	// File contains the cache file
  27  	File *os.File
  28  	// CacheItems contains a list of cache items, all of them have a key
  29  	CacheItems []cacheItem `json:"items"`
  30  	// prevent simultaneous cache writes
  31  	writeLock sync.RWMutex
  32  }
  33  
  34  // NewFileTokenCache opens or creates a filesystem cache File on the specified path
  35  func NewFileTokenCache(path string) (*FileTokenCache, error) {
  36  	// open the File or create a new one on the given location
  37  	file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
  38  	if err != nil {
  39  		return &FileTokenCache{}, fmt.Errorf("error opening cache File: %w", err)
  40  	}
  41  
  42  	cache := FileTokenCache{File: file}
  43  
  44  	// try to read the File
  45  	fileContent, err := io.ReadAll(file)
  46  	if err != nil {
  47  		return &FileTokenCache{}, fmt.Errorf("error reading cache File: %w", err)
  48  	}
  49  
  50  	if len(fileContent) > 0 {
  51  		// read the cached File data as json
  52  		if err := json.Unmarshal(fileContent, &cache); err != nil {
  53  			return &FileTokenCache{}, fmt.Errorf("error unmarshalling cache File: %w", err)
  54  		}
  55  	}
  56  
  57  	return &cache, nil
  58  }
  59  
  60  // Set will save a token by name as jwt.Token
  61  func (f *FileTokenCache) Set(key string, token jwt.Token) error {
  62  	for idx, item := range f.CacheItems {
  63  		if item.Key == key {
  64  			f.CacheItems[idx].Data = []byte(token.String())
  65  
  66  			// persist this change to the cache File
  67  			return f.writeCacheToFile()
  68  		}
  69  	}
  70  
  71  	// if the key did not exist before, we append a new item to the cache item list
  72  	f.CacheItems = append(f.CacheItems, cacheItem{Key: key, Data: []byte(token.String())})
  73  
  74  	return f.writeCacheToFile()
  75  }
  76  
  77  func (f *FileTokenCache) writeCacheToFile() error {
  78  	// try to convert the cache to json, so we can write it to File
  79  	cacheData, err := json.Marshal(f)
  80  	if err != nil {
  81  		return fmt.Errorf("error marshalling cache File: %w", err)
  82  	}
  83  
  84  	f.writeLock.Lock()
  85  	defer f.writeLock.Unlock()
  86  	// write the cache data to the File cache
  87  	if err := f.File.Truncate(0); err != nil {
  88  		return fmt.Errorf("error while truncating cache File: %w", err)
  89  	}
  90  	_, err = f.File.WriteAt(cacheData, 0)
  91  
  92  	return err
  93  }
  94  
  95  // Get a previously acquired token by name returned as jwt.Token
  96  func (f *FileTokenCache) Get(key string) (jwt.Token, error) {
  97  	for _, item := range f.CacheItems {
  98  		if item.Key == key {
  99  			dataAsString := string(item.Data)
 100  
 101  			return jwt.New(dataAsString)
 102  		}
 103  	}
 104  
 105  	return jwt.Token{}, nil
 106  }
 107