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