mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-08-31 23:36:57 +00:00
632 lines
16 KiB
Go
632 lines
16 KiB
Go
package oauth
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/bluesky-social/indigo/atproto/auth/oauth"
|
|
"github.com/bluesky-social/indigo/atproto/syntax"
|
|
)
|
|
|
|
func TestNewFileStore(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/oauth-test.json"
|
|
|
|
store, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() error = %v", err)
|
|
}
|
|
|
|
if store == nil {
|
|
t.Fatal("Expected non-nil store")
|
|
}
|
|
|
|
if store.path != storePath {
|
|
t.Errorf("Expected path %q, got %q", storePath, store.path)
|
|
}
|
|
|
|
if store.sessions == nil {
|
|
t.Error("Expected sessions map to be initialized")
|
|
}
|
|
|
|
if store.requests == nil {
|
|
t.Error("Expected requests map to be initialized")
|
|
}
|
|
}
|
|
|
|
func TestFileStore_LoadNonExistent(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/nonexistent.json"
|
|
|
|
// Should succeed even if file doesn't exist
|
|
store, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() should succeed with non-existent file, got error: %v", err)
|
|
}
|
|
|
|
if store == nil {
|
|
t.Fatal("Expected non-nil store")
|
|
}
|
|
}
|
|
|
|
func TestFileStore_LoadCorruptedFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/corrupted.json"
|
|
|
|
// Create corrupted JSON file
|
|
if err := os.WriteFile(storePath, []byte("invalid json {{{"), 0600); err != nil {
|
|
t.Fatalf("Failed to create corrupted file: %v", err)
|
|
}
|
|
|
|
// Should fail to load corrupted file
|
|
_, err := NewFileStore(storePath)
|
|
if err == nil {
|
|
t.Error("Expected error when loading corrupted file")
|
|
}
|
|
}
|
|
|
|
func TestFileStore_GetSession_NotFound(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/oauth-test.json"
|
|
|
|
store, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() error = %v", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
did, _ := syntax.ParseDID("did:plc:test123")
|
|
sessionID := "session123"
|
|
|
|
// Should return error for non-existent session
|
|
session, err := store.GetSession(ctx, did, sessionID)
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent session")
|
|
}
|
|
if session != nil {
|
|
t.Error("Expected nil session for non-existent entry")
|
|
}
|
|
}
|
|
|
|
func TestFileStore_SaveAndGetSession(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/oauth-test.json"
|
|
|
|
store, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() error = %v", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
did, _ := syntax.ParseDID("did:plc:alice123")
|
|
|
|
// Create test session
|
|
sessionData := oauth.ClientSessionData{
|
|
AccountDID: did,
|
|
SessionID: "test-session-123",
|
|
HostURL: "https://pds.example.com",
|
|
Scopes: []string{"atproto", "blob:read"},
|
|
}
|
|
|
|
// Save session
|
|
if err := store.SaveSession(ctx, sessionData); err != nil {
|
|
t.Fatalf("SaveSession() error = %v", err)
|
|
}
|
|
|
|
// Retrieve session
|
|
retrieved, err := store.GetSession(ctx, did, "test-session-123")
|
|
if err != nil {
|
|
t.Fatalf("GetSession() error = %v", err)
|
|
}
|
|
|
|
if retrieved == nil {
|
|
t.Fatal("Expected non-nil session")
|
|
}
|
|
|
|
if retrieved.SessionID != sessionData.SessionID {
|
|
t.Errorf("Expected sessionID %q, got %q", sessionData.SessionID, retrieved.SessionID)
|
|
}
|
|
|
|
if retrieved.AccountDID.String() != did.String() {
|
|
t.Errorf("Expected DID %q, got %q", did.String(), retrieved.AccountDID.String())
|
|
}
|
|
|
|
if retrieved.HostURL != sessionData.HostURL {
|
|
t.Errorf("Expected hostURL %q, got %q", sessionData.HostURL, retrieved.HostURL)
|
|
}
|
|
}
|
|
|
|
func TestFileStore_UpdateSession(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/oauth-test.json"
|
|
|
|
store, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() error = %v", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
did, _ := syntax.ParseDID("did:plc:alice123")
|
|
|
|
// Save initial session
|
|
sessionData := oauth.ClientSessionData{
|
|
AccountDID: did,
|
|
SessionID: "test-session-123",
|
|
HostURL: "https://pds.example.com",
|
|
Scopes: []string{"atproto"},
|
|
}
|
|
|
|
if err := store.SaveSession(ctx, sessionData); err != nil {
|
|
t.Fatalf("SaveSession() error = %v", err)
|
|
}
|
|
|
|
// Update session with new scopes
|
|
sessionData.Scopes = []string{"atproto", "blob:read", "blob:write"}
|
|
if err := store.SaveSession(ctx, sessionData); err != nil {
|
|
t.Fatalf("SaveSession() (update) error = %v", err)
|
|
}
|
|
|
|
// Retrieve updated session
|
|
retrieved, err := store.GetSession(ctx, did, "test-session-123")
|
|
if err != nil {
|
|
t.Fatalf("GetSession() error = %v", err)
|
|
}
|
|
|
|
if len(retrieved.Scopes) != 3 {
|
|
t.Errorf("Expected 3 scopes, got %d", len(retrieved.Scopes))
|
|
}
|
|
}
|
|
|
|
func TestFileStore_DeleteSession(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/oauth-test.json"
|
|
|
|
store, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() error = %v", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
did, _ := syntax.ParseDID("did:plc:alice123")
|
|
|
|
// Save session
|
|
sessionData := oauth.ClientSessionData{
|
|
AccountDID: did,
|
|
SessionID: "test-session-123",
|
|
HostURL: "https://pds.example.com",
|
|
}
|
|
|
|
if err := store.SaveSession(ctx, sessionData); err != nil {
|
|
t.Fatalf("SaveSession() error = %v", err)
|
|
}
|
|
|
|
// Verify it exists
|
|
if _, err := store.GetSession(ctx, did, "test-session-123"); err != nil {
|
|
t.Fatalf("GetSession() should succeed before delete, got error: %v", err)
|
|
}
|
|
|
|
// Delete session
|
|
if err := store.DeleteSession(ctx, did, "test-session-123"); err != nil {
|
|
t.Fatalf("DeleteSession() error = %v", err)
|
|
}
|
|
|
|
// Verify it's gone
|
|
_, err = store.GetSession(ctx, did, "test-session-123")
|
|
if err == nil {
|
|
t.Error("Expected error after deleting session")
|
|
}
|
|
}
|
|
|
|
func TestFileStore_DeleteNonExistentSession(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/oauth-test.json"
|
|
|
|
store, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() error = %v", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
did, _ := syntax.ParseDID("did:plc:alice123")
|
|
|
|
// Delete non-existent session should not error
|
|
if err := store.DeleteSession(ctx, did, "nonexistent"); err != nil {
|
|
t.Errorf("DeleteSession() on non-existent session should not error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestFileStore_SaveAndGetAuthRequestInfo(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/oauth-test.json"
|
|
|
|
store, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() error = %v", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create test auth request
|
|
did, _ := syntax.ParseDID("did:plc:alice123")
|
|
authRequest := oauth.AuthRequestData{
|
|
State: "test-state-123",
|
|
AuthServerURL: "https://pds.example.com",
|
|
AccountDID: &did,
|
|
Scopes: []string{"atproto", "blob:read"},
|
|
RequestURI: "urn:ietf:params:oauth:request_uri:test123",
|
|
AuthServerTokenEndpoint: "https://pds.example.com/oauth/token",
|
|
}
|
|
|
|
// Save auth request
|
|
if err := store.SaveAuthRequestInfo(ctx, authRequest); err != nil {
|
|
t.Fatalf("SaveAuthRequestInfo() error = %v", err)
|
|
}
|
|
|
|
// Retrieve auth request
|
|
retrieved, err := store.GetAuthRequestInfo(ctx, "test-state-123")
|
|
if err != nil {
|
|
t.Fatalf("GetAuthRequestInfo() error = %v", err)
|
|
}
|
|
|
|
if retrieved == nil {
|
|
t.Fatal("Expected non-nil auth request")
|
|
}
|
|
|
|
if retrieved.State != authRequest.State {
|
|
t.Errorf("Expected state %q, got %q", authRequest.State, retrieved.State)
|
|
}
|
|
|
|
if retrieved.AuthServerURL != authRequest.AuthServerURL {
|
|
t.Errorf("Expected authServerURL %q, got %q", authRequest.AuthServerURL, retrieved.AuthServerURL)
|
|
}
|
|
}
|
|
|
|
func TestFileStore_GetAuthRequestInfo_NotFound(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/oauth-test.json"
|
|
|
|
store, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() error = %v", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Should return error for non-existent request
|
|
_, err = store.GetAuthRequestInfo(ctx, "nonexistent-state")
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent auth request")
|
|
}
|
|
}
|
|
|
|
func TestFileStore_DeleteAuthRequestInfo(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/oauth-test.json"
|
|
|
|
store, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() error = %v", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Save auth request
|
|
authRequest := oauth.AuthRequestData{
|
|
State: "test-state-123",
|
|
AuthServerURL: "https://pds.example.com",
|
|
}
|
|
|
|
if err := store.SaveAuthRequestInfo(ctx, authRequest); err != nil {
|
|
t.Fatalf("SaveAuthRequestInfo() error = %v", err)
|
|
}
|
|
|
|
// Verify it exists
|
|
if _, err := store.GetAuthRequestInfo(ctx, "test-state-123"); err != nil {
|
|
t.Fatalf("GetAuthRequestInfo() should succeed before delete, got error: %v", err)
|
|
}
|
|
|
|
// Delete auth request
|
|
if err := store.DeleteAuthRequestInfo(ctx, "test-state-123"); err != nil {
|
|
t.Fatalf("DeleteAuthRequestInfo() error = %v", err)
|
|
}
|
|
|
|
// Verify it's gone
|
|
_, err = store.GetAuthRequestInfo(ctx, "test-state-123")
|
|
if err == nil {
|
|
t.Error("Expected error after deleting auth request")
|
|
}
|
|
}
|
|
|
|
func TestFileStore_ListSessions(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/oauth-test.json"
|
|
|
|
store, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() error = %v", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Initially empty
|
|
sessions := store.ListSessions()
|
|
if len(sessions) != 0 {
|
|
t.Errorf("Expected 0 sessions, got %d", len(sessions))
|
|
}
|
|
|
|
// Add multiple sessions
|
|
did1, _ := syntax.ParseDID("did:plc:alice123")
|
|
did2, _ := syntax.ParseDID("did:plc:bob456")
|
|
|
|
session1 := oauth.ClientSessionData{
|
|
AccountDID: did1,
|
|
SessionID: "session-1",
|
|
HostURL: "https://pds1.example.com",
|
|
}
|
|
|
|
session2 := oauth.ClientSessionData{
|
|
AccountDID: did2,
|
|
SessionID: "session-2",
|
|
HostURL: "https://pds2.example.com",
|
|
}
|
|
|
|
if err := store.SaveSession(ctx, session1); err != nil {
|
|
t.Fatalf("SaveSession() error = %v", err)
|
|
}
|
|
|
|
if err := store.SaveSession(ctx, session2); err != nil {
|
|
t.Fatalf("SaveSession() error = %v", err)
|
|
}
|
|
|
|
// List sessions
|
|
sessions = store.ListSessions()
|
|
if len(sessions) != 2 {
|
|
t.Errorf("Expected 2 sessions, got %d", len(sessions))
|
|
}
|
|
|
|
// Verify we got both sessions
|
|
key1 := makeSessionKey(did1.String(), "session-1")
|
|
key2 := makeSessionKey(did2.String(), "session-2")
|
|
|
|
if sessions[key1] == nil {
|
|
t.Error("Expected session1 in list")
|
|
}
|
|
|
|
if sessions[key2] == nil {
|
|
t.Error("Expected session2 in list")
|
|
}
|
|
}
|
|
|
|
func TestFileStore_Persistence_Across_Instances(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/oauth-test.json"
|
|
|
|
ctx := context.Background()
|
|
did, _ := syntax.ParseDID("did:plc:alice123")
|
|
|
|
// Create first store and save data
|
|
store1, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() error = %v", err)
|
|
}
|
|
|
|
sessionData := oauth.ClientSessionData{
|
|
AccountDID: did,
|
|
SessionID: "persistent-session",
|
|
HostURL: "https://pds.example.com",
|
|
}
|
|
|
|
if err := store1.SaveSession(ctx, sessionData); err != nil {
|
|
t.Fatalf("SaveSession() error = %v", err)
|
|
}
|
|
|
|
authRequest := oauth.AuthRequestData{
|
|
State: "persistent-state",
|
|
AuthServerURL: "https://pds.example.com",
|
|
}
|
|
|
|
if err := store1.SaveAuthRequestInfo(ctx, authRequest); err != nil {
|
|
t.Fatalf("SaveAuthRequestInfo() error = %v", err)
|
|
}
|
|
|
|
// Create second store from same file
|
|
store2, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("Second NewFileStore() error = %v", err)
|
|
}
|
|
|
|
// Verify session persisted
|
|
retrievedSession, err := store2.GetSession(ctx, did, "persistent-session")
|
|
if err != nil {
|
|
t.Fatalf("GetSession() from second store error = %v", err)
|
|
}
|
|
|
|
if retrievedSession.SessionID != "persistent-session" {
|
|
t.Errorf("Expected persistent session ID, got %q", retrievedSession.SessionID)
|
|
}
|
|
|
|
// Verify auth request persisted
|
|
retrievedAuth, err := store2.GetAuthRequestInfo(ctx, "persistent-state")
|
|
if err != nil {
|
|
t.Fatalf("GetAuthRequestInfo() from second store error = %v", err)
|
|
}
|
|
|
|
if retrievedAuth.State != "persistent-state" {
|
|
t.Errorf("Expected persistent state, got %q", retrievedAuth.State)
|
|
}
|
|
}
|
|
|
|
func TestFileStore_FileSecurity(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/oauth-test.json"
|
|
|
|
store, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() error = %v", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
did, _ := syntax.ParseDID("did:plc:alice123")
|
|
|
|
// Save some data to trigger file creation
|
|
sessionData := oauth.ClientSessionData{
|
|
AccountDID: did,
|
|
SessionID: "test-session",
|
|
HostURL: "https://pds.example.com",
|
|
}
|
|
|
|
if err := store.SaveSession(ctx, sessionData); err != nil {
|
|
t.Fatalf("SaveSession() error = %v", err)
|
|
}
|
|
|
|
// Check file permissions (should be 0600)
|
|
info, err := os.Stat(storePath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to stat file: %v", err)
|
|
}
|
|
|
|
mode := info.Mode()
|
|
if mode.Perm() != 0600 {
|
|
t.Errorf("Expected file permissions 0600, got %o", mode.Perm())
|
|
}
|
|
}
|
|
|
|
func TestFileStore_JSONFormat(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/oauth-test.json"
|
|
|
|
store, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() error = %v", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
did, _ := syntax.ParseDID("did:plc:alice123")
|
|
|
|
// Save data
|
|
sessionData := oauth.ClientSessionData{
|
|
AccountDID: did,
|
|
SessionID: "test-session",
|
|
HostURL: "https://pds.example.com",
|
|
}
|
|
|
|
if err := store.SaveSession(ctx, sessionData); err != nil {
|
|
t.Fatalf("SaveSession() error = %v", err)
|
|
}
|
|
|
|
// Read and verify JSON format
|
|
data, err := os.ReadFile(storePath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read file: %v", err)
|
|
}
|
|
|
|
var storeData FileStoreData
|
|
if err := json.Unmarshal(data, &storeData); err != nil {
|
|
t.Fatalf("Failed to parse JSON: %v", err)
|
|
}
|
|
|
|
if storeData.Sessions == nil {
|
|
t.Error("Expected sessions in JSON")
|
|
}
|
|
|
|
if storeData.Requests == nil {
|
|
t.Error("Expected requests in JSON")
|
|
}
|
|
}
|
|
|
|
func TestFileStore_CleanupExpired(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/oauth-test.json"
|
|
|
|
store, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() error = %v", err)
|
|
}
|
|
|
|
// CleanupExpired should not error even with no data
|
|
if err := store.CleanupExpired(); err != nil {
|
|
t.Errorf("CleanupExpired() error = %v", err)
|
|
}
|
|
|
|
// Note: Current implementation doesn't actually clean anything
|
|
// since AuthRequestData and ClientSessionData don't have expiry timestamps
|
|
// This test verifies the method doesn't panic
|
|
}
|
|
|
|
func TestGetDefaultStorePath(t *testing.T) {
|
|
path, err := GetDefaultStorePath()
|
|
if err != nil {
|
|
t.Fatalf("GetDefaultStorePath() error = %v", err)
|
|
}
|
|
|
|
if path == "" {
|
|
t.Fatal("Expected non-empty path")
|
|
}
|
|
|
|
// Path should either be /var/lib/atcr or ~/.atcr
|
|
// We can't assert exact path since it depends on permissions
|
|
t.Logf("Default store path: %s", path)
|
|
}
|
|
|
|
func TestMakeSessionKey(t *testing.T) {
|
|
did := "did:plc:alice123"
|
|
sessionID := "session-456"
|
|
|
|
key := makeSessionKey(did, sessionID)
|
|
expected := "did:plc:alice123:session-456"
|
|
|
|
if key != expected {
|
|
t.Errorf("Expected key %q, got %q", expected, key)
|
|
}
|
|
}
|
|
|
|
func TestFileStore_ConcurrentAccess(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
storePath := tmpDir + "/oauth-test.json"
|
|
|
|
store, err := NewFileStore(storePath)
|
|
if err != nil {
|
|
t.Fatalf("NewFileStore() error = %v", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Run concurrent operations
|
|
done := make(chan bool)
|
|
|
|
// Writer goroutine
|
|
go func() {
|
|
for i := 0; i < 10; i++ {
|
|
did, _ := syntax.ParseDID("did:plc:alice123")
|
|
sessionData := oauth.ClientSessionData{
|
|
AccountDID: did,
|
|
SessionID: "session-1",
|
|
HostURL: "https://pds.example.com",
|
|
}
|
|
store.SaveSession(ctx, sessionData)
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
done <- true
|
|
}()
|
|
|
|
// Reader goroutine
|
|
go func() {
|
|
for i := 0; i < 10; i++ {
|
|
did, _ := syntax.ParseDID("did:plc:alice123")
|
|
store.GetSession(ctx, did, "session-1")
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
done <- true
|
|
}()
|
|
|
|
// Wait for both goroutines
|
|
<-done
|
|
<-done
|
|
|
|
// If we got here without panicking, the locking works
|
|
t.Log("Concurrent access test passed")
|
|
}
|