mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-19 08:44:14 +00:00
clean up old migration code. minor bug fixes with appview ui
This commit is contained in:
@@ -30,6 +30,16 @@ type HoldAuthorizer interface {
|
||||
// Called when user successfully becomes a crew member to ensure immediate access
|
||||
// Returns nil if no denial cache exists or invalidation succeeds
|
||||
ClearCrewDenial(ctx context.Context, holdDID, userDID string) error
|
||||
|
||||
// IsCachedCrewMember returns true only if there is a non-expired approval
|
||||
// in the cache. It MUST NOT make any network calls. Cache miss returns (false, nil).
|
||||
IsCachedCrewMember(ctx context.Context, holdDID, userDID string) (bool, error)
|
||||
|
||||
// RecordCrewApproval writes an approval to the cache with the implementation's
|
||||
// standard TTL. Used to warm the cache after an out-of-band confirmation of crew
|
||||
// membership (e.g. a successful requestCrew POST). No-op for implementations
|
||||
// without a cache.
|
||||
RecordCrewApproval(ctx context.Context, holdDID, userDID string) error
|
||||
}
|
||||
|
||||
// CheckReadAccessWithCaptain implements the standard read authorization logic
|
||||
|
||||
@@ -401,6 +401,24 @@ func (a *RemoteHoldAuthorizer) isCrewMemberNoCache(ctx context.Context, holdDID,
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// IsCachedCrewMember returns true if there is a non-expired approval row.
|
||||
// Never makes network calls. Cache miss or no DB returns (false, nil).
|
||||
func (a *RemoteHoldAuthorizer) IsCachedCrewMember(ctx context.Context, holdDID, userDID string) (bool, error) {
|
||||
if a.db == nil {
|
||||
return false, nil
|
||||
}
|
||||
return a.getCachedApproval(holdDID, userDID)
|
||||
}
|
||||
|
||||
// RecordCrewApproval writes an approval to the cache with the standard 15-min TTL.
|
||||
// No-op if there is no DB.
|
||||
func (a *RemoteHoldAuthorizer) RecordCrewApproval(ctx context.Context, holdDID, userDID string) error {
|
||||
if a.db == nil {
|
||||
return nil
|
||||
}
|
||||
return a.cacheApproval(holdDID, userDID, 15*time.Minute)
|
||||
}
|
||||
|
||||
// CheckReadAccess implements read authorization using shared logic
|
||||
func (a *RemoteHoldAuthorizer) CheckReadAccess(ctx context.Context, holdDID, userDID string) (bool, error) {
|
||||
captain, err := a.GetCaptainRecord(ctx, holdDID)
|
||||
|
||||
@@ -446,3 +446,129 @@ func TestClearAllDenials_OnStartup(t *testing.T) {
|
||||
t.Error("Expected all denials to be cleared after ClearAllDenials")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCachedCrewMember_Hit(t *testing.T) {
|
||||
testDB := setupTestDB(t)
|
||||
remote := NewRemoteHoldAuthorizer(testDB, false).(*RemoteHoldAuthorizer)
|
||||
defer close(remote.stopCleanup)
|
||||
|
||||
holdDID := "did:web:hold01.atcr.io"
|
||||
userDID := "did:plc:user123"
|
||||
|
||||
if err := remote.cacheApproval(holdDID, userDID, 15*time.Minute); err != nil {
|
||||
t.Fatalf("cacheApproval failed: %v", err)
|
||||
}
|
||||
|
||||
cached, err := remote.IsCachedCrewMember(context.Background(), holdDID, userDID)
|
||||
if err != nil {
|
||||
t.Fatalf("IsCachedCrewMember returned error: %v", err)
|
||||
}
|
||||
if !cached {
|
||||
t.Error("Expected cache hit, got miss")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCachedCrewMember_Miss(t *testing.T) {
|
||||
testDB := setupTestDB(t)
|
||||
remote := NewRemoteHoldAuthorizer(testDB, false).(*RemoteHoldAuthorizer)
|
||||
defer close(remote.stopCleanup)
|
||||
|
||||
cached, err := remote.IsCachedCrewMember(context.Background(),
|
||||
"did:web:hold01.atcr.io", "did:plc:nobody")
|
||||
if err != nil {
|
||||
t.Fatalf("IsCachedCrewMember returned error: %v", err)
|
||||
}
|
||||
if cached {
|
||||
t.Error("Expected cache miss, got hit")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCachedCrewMember_Expired(t *testing.T) {
|
||||
testDB := setupTestDB(t)
|
||||
remote := NewRemoteHoldAuthorizer(testDB, false).(*RemoteHoldAuthorizer)
|
||||
defer close(remote.stopCleanup)
|
||||
|
||||
holdDID := "did:web:hold01.atcr.io"
|
||||
userDID := "did:plc:user123"
|
||||
|
||||
// Insert a row that already expired one minute ago.
|
||||
now := time.Now()
|
||||
_, err := testDB.Exec(`
|
||||
INSERT INTO hold_crew_approvals (hold_did, user_did, approved_at, expires_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`, holdDID, userDID, now.Add(-2*time.Minute), now.Add(-1*time.Minute))
|
||||
if err != nil {
|
||||
t.Fatalf("seed insert failed: %v", err)
|
||||
}
|
||||
|
||||
cached, err := remote.IsCachedCrewMember(context.Background(), holdDID, userDID)
|
||||
if err != nil {
|
||||
t.Fatalf("IsCachedCrewMember returned error: %v", err)
|
||||
}
|
||||
if cached {
|
||||
t.Error("Expected expired entry to be treated as miss")
|
||||
}
|
||||
|
||||
// Expired entry should have been cleaned up by getCachedApproval.
|
||||
var count int
|
||||
if err := testDB.QueryRow(`
|
||||
SELECT COUNT(*) FROM hold_crew_approvals WHERE hold_did = ? AND user_did = ?
|
||||
`, holdDID, userDID).Scan(&count); err != nil {
|
||||
t.Fatalf("count query failed: %v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Errorf("Expected expired row to be deleted, found %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordCrewApproval_WritesAndReadsBack(t *testing.T) {
|
||||
testDB := setupTestDB(t)
|
||||
remote := NewRemoteHoldAuthorizer(testDB, false).(*RemoteHoldAuthorizer)
|
||||
defer close(remote.stopCleanup)
|
||||
|
||||
holdDID := "did:web:hold01.atcr.io"
|
||||
userDID := "did:plc:user123"
|
||||
|
||||
if err := remote.RecordCrewApproval(context.Background(), holdDID, userDID); err != nil {
|
||||
t.Fatalf("RecordCrewApproval failed: %v", err)
|
||||
}
|
||||
|
||||
cached, err := remote.IsCachedCrewMember(context.Background(), holdDID, userDID)
|
||||
if err != nil {
|
||||
t.Fatalf("IsCachedCrewMember returned error: %v", err)
|
||||
}
|
||||
if !cached {
|
||||
t.Error("Expected RecordCrewApproval to populate the cache")
|
||||
}
|
||||
|
||||
// Verify TTL is roughly 15 minutes from now.
|
||||
var expiresAt time.Time
|
||||
if err := testDB.QueryRow(`
|
||||
SELECT expires_at FROM hold_crew_approvals WHERE hold_did = ? AND user_did = ?
|
||||
`, holdDID, userDID).Scan(&expiresAt); err != nil {
|
||||
t.Fatalf("expires_at query failed: %v", err)
|
||||
}
|
||||
ttl := time.Until(expiresAt)
|
||||
if ttl < 14*time.Minute || ttl > 16*time.Minute {
|
||||
t.Errorf("Expected TTL ~15min, got %v", ttl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCachedCrewMember_NoDB(t *testing.T) {
|
||||
remote := NewRemoteHoldAuthorizer(nil, false).(*RemoteHoldAuthorizer)
|
||||
defer close(remote.stopCleanup)
|
||||
|
||||
cached, err := remote.IsCachedCrewMember(context.Background(),
|
||||
"did:web:hold01.atcr.io", "did:plc:user123")
|
||||
if err != nil {
|
||||
t.Errorf("Expected nil error with nil DB, got %v", err)
|
||||
}
|
||||
if cached {
|
||||
t.Error("Expected false with nil DB")
|
||||
}
|
||||
|
||||
if err := remote.RecordCrewApproval(context.Background(),
|
||||
"did:web:hold01.atcr.io", "did:plc:user123"); err != nil {
|
||||
t.Errorf("Expected nil error from RecordCrewApproval with nil DB, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,3 +102,14 @@ func (a *Authorizer) CheckWriteAccess(ctx context.Context, holdDID, userDID stri
|
||||
func (a *Authorizer) ClearCrewDenial(ctx context.Context, holdDID, userDID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsCachedCrewMember always returns (false, nil) for the local authorizer.
|
||||
// There is no cache; callers fall through to the direct PDS lookup.
|
||||
func (a *Authorizer) IsCachedCrewMember(ctx context.Context, holdDID, userDID string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// RecordCrewApproval is a no-op for the local authorizer (no cache to warm).
|
||||
func (a *Authorizer) RecordCrewApproval(ctx context.Context, holdDID, userDID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user