131 lines
3.8 KiB
Go
131 lines
3.8 KiB
Go
package utils
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
|
|
"github.com/go-errors/errors"
|
|
"github.com/spf13/afero"
|
|
"github.com/supabase/cli/internal/utils/credentials"
|
|
"github.com/zalando/go-keyring"
|
|
)
|
|
|
|
var (
|
|
AccessTokenPattern = regexp.MustCompile(`^sbp_(oauth_)?[a-f0-9]{40}$`)
|
|
ErrInvalidToken = errors.New("Invalid access token format. Must be like `sbp_0102...1920`.")
|
|
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"))
|
|
ErrNotLoggedIn = errors.New("You were not logged in, nothing to do.")
|
|
)
|
|
|
|
const AccessTokenKey = "access-token"
|
|
|
|
func LoadAccessToken() (string, error) {
|
|
return LoadAccessTokenFS(afero.NewOsFs())
|
|
}
|
|
|
|
func LoadAccessTokenFS(fsys afero.Fs) (string, error) {
|
|
accessToken, err := loadAccessToken(fsys)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !AccessTokenPattern.MatchString(accessToken) {
|
|
return "", errors.New(ErrInvalidToken)
|
|
}
|
|
return accessToken, nil
|
|
}
|
|
|
|
func loadAccessToken(fsys afero.Fs) (string, error) {
|
|
// Env takes precedence
|
|
if accessToken := os.Getenv("SUPABASE_ACCESS_TOKEN"); accessToken != "" {
|
|
return accessToken, nil
|
|
}
|
|
// Load from native credentials store
|
|
if accessToken, err := credentials.StoreProvider.Get(AccessTokenKey); err == nil {
|
|
return accessToken, nil
|
|
}
|
|
// Fallback to token file
|
|
return fallbackLoadToken(fsys)
|
|
}
|
|
|
|
func fallbackLoadToken(fsys afero.Fs) (string, error) {
|
|
path, err := getAccessTokenPath()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
accessToken, err := afero.ReadFile(fsys, path)
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return "", errors.New(ErrMissingToken)
|
|
} else if err != nil {
|
|
return "", errors.Errorf("failed to read access token file: %w", err)
|
|
}
|
|
return string(accessToken), nil
|
|
}
|
|
|
|
func SaveAccessToken(accessToken string, fsys afero.Fs) error {
|
|
// Validate access token
|
|
if !AccessTokenPattern.MatchString(accessToken) {
|
|
return errors.New(ErrInvalidToken)
|
|
}
|
|
// Save to native credentials store
|
|
if err := credentials.StoreProvider.Set(AccessTokenKey, accessToken); err == nil {
|
|
return nil
|
|
}
|
|
// Fallback to token file
|
|
return fallbackSaveToken(accessToken, fsys)
|
|
}
|
|
|
|
func fallbackSaveToken(accessToken string, fsys afero.Fs) error {
|
|
path, err := getAccessTokenPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := MkdirIfNotExistFS(fsys, filepath.Dir(path)); err != nil {
|
|
return err
|
|
}
|
|
if err := afero.WriteFile(fsys, path, []byte(accessToken), 0600); err != nil {
|
|
return errors.Errorf("failed to save access token file: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func DeleteAccessToken(fsys afero.Fs) error {
|
|
// Always delete the fallback token file to handle legacy CLI
|
|
if err := fallbackDeleteToken(fsys); err == nil {
|
|
// Typically user system should only have either token file or keyring.
|
|
// But we delete from both just in case.
|
|
_ = credentials.StoreProvider.Delete(AccessTokenKey)
|
|
return nil
|
|
} else if !errors.Is(err, os.ErrNotExist) {
|
|
return err
|
|
}
|
|
// Fallback not found, delete from native credentials store
|
|
err := credentials.StoreProvider.Delete(AccessTokenKey)
|
|
if errors.Is(err, credentials.ErrNotSupported) || errors.Is(err, keyring.ErrNotFound) {
|
|
return errors.New(ErrNotLoggedIn)
|
|
} else if err != nil {
|
|
return errors.Errorf("failed to delete access token from keyring: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func fallbackDeleteToken(fsys afero.Fs) error {
|
|
path, err := getAccessTokenPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := fsys.Remove(path); err != nil {
|
|
return errors.Errorf("failed to remove access token file: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getAccessTokenPath() (string, error) {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", errors.Errorf("failed to get $HOME directory: %w", err)
|
|
}
|
|
// TODO: fallback to workdir
|
|
return filepath.Join(home, ".supabase", AccessTokenKey), nil
|
|
}
|