code.vegaprotocol.io/vega@v0.79.0/wallet/service/v2/connections/store/session/v1/file_store.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package v1 17 18 import ( 19 "context" 20 "fmt" 21 "sort" 22 "sync" 23 24 vgfs "code.vegaprotocol.io/vega/libs/fs" 25 "code.vegaprotocol.io/vega/paths" 26 "code.vegaprotocol.io/vega/wallet/service/v2/connections" 27 ) 28 29 type FileStore struct { 30 sessionsFilePath string 31 32 mu sync.Mutex 33 } 34 35 func (s *FileStore) ListSessions(_ context.Context) ([]connections.Session, error) { 36 s.mu.Lock() 37 defer s.mu.Unlock() 38 39 sessionsFile, err := s.readSessionsFile() 40 if err != nil { 41 return nil, err 42 } 43 44 sessions := make([]connections.Session, 0, len(sessionsFile.Sessions)) 45 46 for rawToken, session := range sessionsFile.Sessions { 47 token, err := connections.AsToken(rawToken) 48 if err != nil { 49 return nil, fmt.Errorf("token %q is not a valid token: %w", rawToken, err) 50 } 51 sessions = append(sessions, connections.Session{ 52 Token: token, 53 Hostname: session.Hostname, 54 Wallet: session.Wallet, 55 }) 56 } 57 58 sort.SliceStable(sessions, func(i, j int) bool { 59 if sessions[i].Hostname == sessions[j].Hostname { 60 return sessions[i].Wallet < sessions[j].Wallet 61 } 62 63 return sessions[i].Hostname < sessions[j].Hostname 64 }) 65 66 return sessions, nil 67 } 68 69 func (s *FileStore) TrackSession(session connections.Session) error { 70 s.mu.Lock() 71 defer s.mu.Unlock() 72 73 tokensFile, err := s.readSessionsFile() 74 if err != nil { 75 return err 76 } 77 78 tokensFile.Sessions[session.Token.String()] = sessionContent{ 79 Hostname: session.Hostname, 80 Wallet: session.Wallet, 81 } 82 83 return s.writeSessionsFile(tokensFile) 84 } 85 86 func (s *FileStore) DeleteSession(_ context.Context, token connections.Token) error { 87 s.mu.Lock() 88 defer s.mu.Unlock() 89 90 sessionsFile, err := s.readSessionsFile() 91 if err != nil { 92 return err 93 } 94 95 delete(sessionsFile.Sessions, token.String()) 96 97 return s.writeSessionsFile(sessionsFile) 98 } 99 100 func (s *FileStore) readSessionsFile() (sessions sessionsFile, rerr error) { 101 defer func() { 102 if r := recover(); r != nil { 103 sessions, rerr = sessionsFile{}, fmt.Errorf("a system error occurred while reading the tokens file: %s", r) 104 } 105 }() 106 107 exists, err := vgfs.FileExists(s.sessionsFilePath) 108 if err != nil { 109 return sessionsFile{}, fmt.Errorf("could not verify the existence of the tokens file: %w", err) 110 } else if !exists { 111 return defaultSessionsFileContent(), nil 112 } 113 114 if err := paths.ReadStructuredFile(s.sessionsFilePath, &sessions); err != nil { 115 return sessionsFile{}, fmt.Errorf("couldn't read the sessions file %s: %w", s.sessionsFilePath, err) 116 } 117 118 if sessions.FileVersion != 1 { 119 return sessionsFile{}, fmt.Errorf("the sessions file is using the file format v%d but that format is not supported by this application", sessions.FileVersion) 120 } 121 122 if sessions.TokensVersion != 1 { 123 return sessionsFile{}, fmt.Errorf("the tokens used in sessions are using the token format v%d but that format is not supported by this application", sessions.TokensVersion) 124 } 125 126 if sessions.Sessions == nil { 127 sessions.Sessions = map[string]sessionContent{} 128 } 129 130 return sessions, nil 131 } 132 133 func (s *FileStore) writeSessionsFile(sessions sessionsFile) (rerr error) { 134 defer func() { 135 if r := recover(); r != nil { 136 rerr = fmt.Errorf("a system error occurred while writing the sessions file:: %s", r) 137 } 138 }() 139 if err := paths.WriteStructuredFile(s.sessionsFilePath, sessions); err != nil { 140 return fmt.Errorf("couldn't write the sessions file %s: %w", s.sessionsFilePath, err) 141 } 142 143 return nil 144 } 145 146 func InitialiseStore(p paths.Paths) (*FileStore, error) { 147 sessionsFilePath, err := p.CreateDataPathFor(paths.WalletServiceSessionTokensDataFile) 148 if err != nil { 149 return nil, fmt.Errorf("couldn't get data path for %s: %w", paths.WalletServiceSessionTokensDataFile, err) 150 } 151 152 store := &FileStore{ 153 sessionsFilePath: sessionsFilePath, 154 } 155 156 exists, err := vgfs.FileExists(sessionsFilePath) 157 if err != nil { 158 return nil, fmt.Errorf("could not verify wether the session file exists or not: %w", err) 159 } 160 if !exists { 161 err := paths.WriteStructuredFile(sessionsFilePath, defaultSessionsFileContent()) 162 if err != nil { 163 return nil, fmt.Errorf("could not initialise the sessions file: %w", err) 164 } 165 } 166 167 if _, err := store.readSessionsFile(); err != nil { 168 return nil, err 169 } 170 171 return store, nil 172 } 173 174 type sessionsFile struct { 175 FileVersion int `json:"fileVersion"` 176 TokensVersion int `json:"tokensVersion"` 177 Sessions map[string]sessionContent `json:"sessions"` 178 } 179 180 type sessionContent struct { 181 Hostname string `json:"hostname"` 182 Wallet string `json:"wallet"` 183 } 184 185 func defaultSessionsFileContent() sessionsFile { 186 return sessionsFile{ 187 FileVersion: 1, 188 TokensVersion: 1, 189 Sessions: map[string]sessionContent{}, 190 } 191 }