github.com/supabase/cli@v1.168.1/internal/utils/access_token.go (about) 1 package utils 2 3 import ( 4 "os" 5 "path/filepath" 6 "regexp" 7 8 "github.com/go-errors/errors" 9 "github.com/spf13/afero" 10 "github.com/supabase/cli/internal/utils/credentials" 11 "github.com/zalando/go-keyring" 12 ) 13 14 var ( 15 AccessTokenPattern = regexp.MustCompile(`^sbp_[a-f0-9]{40}$`) 16 ErrInvalidToken = errors.New("Invalid access token format. Must be like `sbp_0102...1920`.") 17 ErrMissingToken = errors.Errorf("Access token not provided. Supply an access token by running %s or setting the SUPABASE_ACCESS_TOKEN environment variable.", Aqua("supabase login")) 18 ErrNotLoggedIn = errors.New("You were not logged in, nothing to do.") 19 ) 20 21 const AccessTokenKey = "access-token" 22 23 func LoadAccessToken() (string, error) { 24 return LoadAccessTokenFS(afero.NewOsFs()) 25 } 26 27 func LoadAccessTokenFS(fsys afero.Fs) (string, error) { 28 accessToken, err := loadAccessToken(fsys) 29 if err != nil { 30 return "", err 31 } 32 if !AccessTokenPattern.MatchString(accessToken) { 33 return "", errors.New(ErrInvalidToken) 34 } 35 return accessToken, nil 36 } 37 38 func loadAccessToken(fsys afero.Fs) (string, error) { 39 // Env takes precedence 40 if accessToken := os.Getenv("SUPABASE_ACCESS_TOKEN"); accessToken != "" { 41 return accessToken, nil 42 } 43 // Load from native credentials store 44 if accessToken, err := credentials.Get(AccessTokenKey); err == nil { 45 return accessToken, nil 46 } 47 // Fallback to token file 48 return fallbackLoadToken(fsys) 49 } 50 51 func fallbackLoadToken(fsys afero.Fs) (string, error) { 52 path, err := getAccessTokenPath() 53 if err != nil { 54 return "", err 55 } 56 accessToken, err := afero.ReadFile(fsys, path) 57 if errors.Is(err, os.ErrNotExist) { 58 return "", errors.New(ErrMissingToken) 59 } else if err != nil { 60 return "", errors.Errorf("failed to read access token file: %w", err) 61 } 62 return string(accessToken), nil 63 } 64 65 func SaveAccessToken(accessToken string, fsys afero.Fs) error { 66 // Validate access token 67 if !AccessTokenPattern.MatchString(accessToken) { 68 return errors.New(ErrInvalidToken) 69 } 70 // Save to native credentials store 71 if err := credentials.Set(AccessTokenKey, accessToken); err == nil { 72 return nil 73 } 74 // Fallback to token file 75 return fallbackSaveToken(accessToken, fsys) 76 } 77 78 func fallbackSaveToken(accessToken string, fsys afero.Fs) error { 79 path, err := getAccessTokenPath() 80 if err != nil { 81 return err 82 } 83 if err := MkdirIfNotExistFS(fsys, filepath.Dir(path)); err != nil { 84 return err 85 } 86 if err := afero.WriteFile(fsys, path, []byte(accessToken), 0600); err != nil { 87 return errors.Errorf("failed to save access token file: %w", err) 88 } 89 return nil 90 } 91 92 func DeleteAccessToken(fsys afero.Fs) error { 93 // Always delete the fallback token file to handle legacy CLI 94 if err := fallbackDeleteToken(fsys); err == nil { 95 // Typically user system should only have either token file or keyring. 96 // But we delete from both just in case. 97 _ = credentials.Delete(AccessTokenKey) 98 return nil 99 } else if !errors.Is(err, os.ErrNotExist) { 100 return err 101 } 102 // Fallback not found, delete from native credentials store 103 err := credentials.Delete(AccessTokenKey) 104 if errors.Is(err, credentials.ErrNotSupported) || errors.Is(err, keyring.ErrNotFound) { 105 return errors.New(ErrNotLoggedIn) 106 } else if err != nil { 107 return errors.Errorf("failed to delete access token from keyring: %w", err) 108 } 109 return nil 110 } 111 112 func fallbackDeleteToken(fsys afero.Fs) error { 113 path, err := getAccessTokenPath() 114 if err != nil { 115 return err 116 } 117 if err := fsys.Remove(path); err != nil { 118 return errors.Errorf("failed to remove access token file: %w", err) 119 } 120 return nil 121 } 122 123 func getAccessTokenPath() (string, error) { 124 home, err := os.UserHomeDir() 125 if err != nil { 126 return "", errors.Errorf("failed to get $HOME directory: %w", err) 127 } 128 // TODO: fallback to workdir 129 return filepath.Join(home, ".supabase", AccessTokenKey), nil 130 }