mirror of
https://github.com/sstent/go-garminconnect.git
synced 2026-02-06 06:22:06 +00:00
sync
This commit is contained in:
@@ -4,82 +4,53 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dghubble/oauth1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFileStorage(t *testing.T) {
|
||||
// Create temp directory for tests
|
||||
tempDir, err := os.MkdirTemp("", "garmin-test")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
// Setup
|
||||
tempDir := t.TempDir()
|
||||
storage := NewFileStorage()
|
||||
storage.Path = filepath.Join(tempDir, "token.json")
|
||||
|
||||
storage := &FileStorage{
|
||||
Path: filepath.Join(tempDir, "token.json"),
|
||||
}
|
||||
|
||||
// Test saving and loading token
|
||||
t.Run("SaveAndLoadToken", func(t *testing.T) {
|
||||
testToken := &oauth1.Token{
|
||||
Token: "access-token",
|
||||
TokenSecret: "access-secret",
|
||||
t.Run("SaveToken and GetToken", func(t *testing.T) {
|
||||
token := &oauth1.Token{
|
||||
Token: "test_token",
|
||||
TokenSecret: "test_secret",
|
||||
}
|
||||
|
||||
// Save token
|
||||
err := storage.SaveToken(testToken)
|
||||
assert.NoError(t, err)
|
||||
err := storage.SaveToken(token)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Load token
|
||||
loadedToken, err := storage.GetToken()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testToken.Token, loadedToken.Token)
|
||||
assert.Equal(t, testToken.TokenSecret, loadedToken.TokenSecret)
|
||||
// Get token
|
||||
retrievedToken, err := storage.GetToken()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, token.Token, retrievedToken.Token)
|
||||
assert.Equal(t, token.TokenSecret, retrievedToken.TokenSecret)
|
||||
})
|
||||
|
||||
// Test missing token file
|
||||
t.Run("TokenMissing", func(t *testing.T) {
|
||||
t.Run("EmptyToken", func(t *testing.T) {
|
||||
token := &oauth1.Token{
|
||||
Token: "",
|
||||
TokenSecret: "",
|
||||
}
|
||||
|
||||
err := storage.SaveToken(token)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = storage.GetToken()
|
||||
require.ErrorIs(t, err, os.ErrNotExist)
|
||||
})
|
||||
|
||||
t.Run("NonExistentFile", func(t *testing.T) {
|
||||
storage.Path = filepath.Join(tempDir, "nonexistent.json")
|
||||
_, err := storage.GetToken()
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
})
|
||||
|
||||
// Test token expiration
|
||||
t.Run("TokenExpiration", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
token *oauth1.Token
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "EmptyToken",
|
||||
token: &oauth1.Token{},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "ValidToken",
|
||||
token: &oauth1.Token{
|
||||
Token: "valid",
|
||||
TokenSecret: "valid",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "ExpiredToken",
|
||||
token: &oauth1.Token{
|
||||
Token: "expired",
|
||||
TokenSecret: "expired",
|
||||
CreatedAt: time.Now().Add(-200 * 24 * time.Hour), // 200 days ago
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
expired := storage.TokenExpired(tc.token)
|
||||
assert.Equal(t, tc.expected, expired)
|
||||
})
|
||||
}
|
||||
require.ErrorIs(t, err, os.ErrNotExist)
|
||||
})
|
||||
}
|
||||
|
||||
81
internal/auth/mfastate.go
Normal file
81
internal/auth/mfastate.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MFAState represents the state of an MFA verification session
|
||||
type MFAState struct {
|
||||
VerificationURL string `json:"verification_url"`
|
||||
SessionToken string `json:"session_token"`
|
||||
MFACode string `json:"mfa_code"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
// MFAStorage handles persistence of MFA state
|
||||
type MFAStorage interface {
|
||||
Store(state MFAState) error
|
||||
Get() (MFAState, error)
|
||||
Clear() error
|
||||
}
|
||||
|
||||
// FileMFAStorage implements MFAStorage using a JSON file
|
||||
type FileMFAStorage struct {
|
||||
filePath string
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewFileMFAStorage creates a new file-based MFA storage
|
||||
func NewFileMFAStorage() *FileMFAStorage {
|
||||
home, _ := os.UserHomeDir()
|
||||
return &FileMFAStorage{
|
||||
filePath: filepath.Join(home, ".garminconnect", "mfa_state.json"),
|
||||
}
|
||||
}
|
||||
|
||||
// Store saves MFA state to file
|
||||
func (s *FileMFAStorage) Store(state MFAState) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
// Create directory if needed
|
||||
dir := filepath.Dir(s.filePath)
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(state, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(s.filePath, data, 0600)
|
||||
}
|
||||
|
||||
// Get retrieves MFA state from file
|
||||
func (s *FileMFAStorage) Get() (MFAState, error) {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
data, err := os.ReadFile(s.filePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return MFAState{}, nil
|
||||
}
|
||||
return MFAState{}, err
|
||||
}
|
||||
|
||||
var state MFAState
|
||||
err = json.Unmarshal(data, &state)
|
||||
return state, err
|
||||
}
|
||||
|
||||
// Clear removes the MFA state file
|
||||
func (s *FileMFAStorage) Clear() error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
return os.Remove(s.filePath)
|
||||
}
|
||||
Reference in New Issue
Block a user