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