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  }