A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

try and trace oauth failures

+206 -3
+97
pkg/appview/db/oauth_store.go
··· 337 337 return true 338 338 } 339 339 340 + // GetSessionStats returns statistics about stored OAuth sessions 341 + // Useful for monitoring and debugging session health 342 + func (s *OAuthStore) GetSessionStats(ctx context.Context) (map[string]interface{}, error) { 343 + stats := make(map[string]interface{}) 344 + 345 + // Total sessions 346 + var totalSessions int 347 + err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM oauth_sessions`).Scan(&totalSessions) 348 + if err != nil { 349 + return nil, fmt.Errorf("failed to count sessions: %w", err) 350 + } 351 + stats["total_sessions"] = totalSessions 352 + 353 + // Sessions by age 354 + var sessionsOlderThan1Hour, sessionsOlderThan1Day, sessionsOlderThan7Days int 355 + 356 + err = s.db.QueryRowContext(ctx, ` 357 + SELECT COUNT(*) FROM oauth_sessions 358 + WHERE updated_at < datetime('now', '-1 hour') 359 + `).Scan(&sessionsOlderThan1Hour) 360 + if err == nil { 361 + stats["sessions_idle_1h+"] = sessionsOlderThan1Hour 362 + } 363 + 364 + err = s.db.QueryRowContext(ctx, ` 365 + SELECT COUNT(*) FROM oauth_sessions 366 + WHERE updated_at < datetime('now', '-1 day') 367 + `).Scan(&sessionsOlderThan1Day) 368 + if err == nil { 369 + stats["sessions_idle_1d+"] = sessionsOlderThan1Day 370 + } 371 + 372 + err = s.db.QueryRowContext(ctx, ` 373 + SELECT COUNT(*) FROM oauth_sessions 374 + WHERE updated_at < datetime('now', '-7 days') 375 + `).Scan(&sessionsOlderThan7Days) 376 + if err == nil { 377 + stats["sessions_idle_7d+"] = sessionsOlderThan7Days 378 + } 379 + 380 + // Recent sessions (updated in last 5 minutes) 381 + var recentSessions int 382 + err = s.db.QueryRowContext(ctx, ` 383 + SELECT COUNT(*) FROM oauth_sessions 384 + WHERE updated_at > datetime('now', '-5 minutes') 385 + `).Scan(&recentSessions) 386 + if err == nil { 387 + stats["sessions_active_5m"] = recentSessions 388 + } 389 + 390 + return stats, nil 391 + } 392 + 393 + // ListSessionsForMonitoring returns a list of all sessions with basic info for monitoring 394 + // Returns: DID, session age (minutes), last update time 395 + func (s *OAuthStore) ListSessionsForMonitoring(ctx context.Context) ([]map[string]interface{}, error) { 396 + rows, err := s.db.QueryContext(ctx, ` 397 + SELECT 398 + account_did, 399 + session_id, 400 + created_at, 401 + updated_at, 402 + CAST((julianday('now') - julianday(updated_at)) * 24 * 60 AS INTEGER) as idle_minutes 403 + FROM oauth_sessions 404 + ORDER BY updated_at DESC 405 + `) 406 + if err != nil { 407 + return nil, fmt.Errorf("failed to query sessions: %w", err) 408 + } 409 + defer rows.Close() 410 + 411 + var sessions []map[string]interface{} 412 + for rows.Next() { 413 + var did, sessionID, createdAt, updatedAt string 414 + var idleMinutes int 415 + 416 + if err := rows.Scan(&did, &sessionID, &createdAt, &updatedAt, &idleMinutes); err != nil { 417 + slog.Warn("Failed to scan session row", "error", err) 418 + continue 419 + } 420 + 421 + sessions = append(sessions, map[string]interface{}{ 422 + "did": did, 423 + "session_id": sessionID, 424 + "created_at": createdAt, 425 + "updated_at": updatedAt, 426 + "idle_minutes": idleMinutes, 427 + }) 428 + } 429 + 430 + if err := rows.Err(); err != nil { 431 + return nil, fmt.Errorf("error iterating sessions: %w", err) 432 + } 433 + 434 + return sessions, nil 435 + } 436 + 340 437 // makeSessionKey creates a composite key for session storage 341 438 func makeSessionKey(did, sessionID string) string { 342 439 return fmt.Sprintf("%s:%s", did, sessionID)
+15 -3
pkg/appview/middleware/registry.go
··· 167 167 var err error 168 168 serviceToken, err = token.GetOrFetchServiceToken(ctx, nr.refresher, did, holdDID, pdsEndpoint) 169 169 if err != nil { 170 - slog.Error("Failed to get service token", "component", "registry/middleware", "did", did, "error", err) 171 - slog.Error("User needs to re-authenticate via credential helper", "component", "registry/middleware") 172 - return nil, nr.authErrorMessage("OAuth session expired") 170 + slog.Error("Failed to get service token", 171 + "component", "registry/middleware", 172 + "did", did, 173 + "holdDID", holdDID, 174 + "pdsEndpoint", pdsEndpoint, 175 + "error", err) 176 + 177 + // Check if this is likely an OAuth session expiration 178 + errMsg := err.Error() 179 + if strings.Contains(errMsg, "OAuth session") || strings.Contains(errMsg, "OAuth validation") { 180 + return nil, nr.authErrorMessage("OAuth session expired or invalidated by PDS. Your session has been cleared") 181 + } 182 + 183 + // Generic service token error 184 + return nil, nr.authErrorMessage(fmt.Sprintf("Failed to obtain storage credentials: %v", err)) 173 185 } 174 186 } 175 187
+50
pkg/auth/oauth/client.go
··· 231 231 } 232 232 return session, nil 233 233 } 234 + 235 + // DeleteSession removes an OAuth session from storage and optionally invalidates the UI session 236 + // This is called when OAuth authentication fails to force re-authentication 237 + func (r *Refresher) DeleteSession(ctx context.Context, did string) error { 238 + // Parse DID 239 + accountDID, err := syntax.ParseDID(did) 240 + if err != nil { 241 + return fmt.Errorf("failed to parse DID: %w", err) 242 + } 243 + 244 + // Get the session ID before deleting (for logging) 245 + type sessionGetter interface { 246 + GetLatestSessionForDID(ctx context.Context, did string) (*oauth.ClientSessionData, string, error) 247 + } 248 + 249 + getter, ok := r.clientApp.Store.(sessionGetter) 250 + if !ok { 251 + return fmt.Errorf("store must implement GetLatestSessionForDID") 252 + } 253 + 254 + _, sessionID, err := getter.GetLatestSessionForDID(ctx, did) 255 + if err != nil { 256 + // No session to delete - this is fine 257 + slog.Debug("No OAuth session to delete", "did", did) 258 + return nil 259 + } 260 + 261 + // Delete OAuth session from database 262 + if err := r.clientApp.Store.DeleteSession(ctx, accountDID, sessionID); err != nil { 263 + slog.Warn("Failed to delete OAuth session", "did", did, "sessionID", sessionID, "error", err) 264 + return fmt.Errorf("failed to delete OAuth session: %w", err) 265 + } 266 + 267 + slog.Info("Deleted stale OAuth session", 268 + "component", "oauth/refresher", 269 + "did", did, 270 + "sessionID", sessionID, 271 + "reason", "OAuth authentication failed") 272 + 273 + // Also invalidate the UI session if store is configured 274 + if r.uiSessionStore != nil { 275 + r.uiSessionStore.DeleteByDID(did) 276 + slog.Info("Invalidated UI session for DID", 277 + "component", "oauth/refresher", 278 + "did", did, 279 + "reason", "OAuth session deleted") 280 + } 281 + 282 + return nil 283 + }
+44
pkg/auth/token/servicetoken.go
··· 48 48 if err != nil { 49 49 // OAuth session unavailable - fail 50 50 InvalidateServiceToken(did, holdDID) 51 + slog.Error("Failed to get OAuth session for service token", 52 + "component", "token/servicetoken", 53 + "did", did, 54 + "holdDID", holdDID, 55 + "pdsEndpoint", pdsEndpoint, 56 + "error", err, 57 + "errorType", fmt.Sprintf("%T", err)) 58 + 59 + // Delete the stale OAuth session to force re-authentication 60 + // This also invalidates the UI session automatically 61 + if delErr := refresher.DeleteSession(ctx, did); delErr != nil { 62 + slog.Warn("Failed to delete stale OAuth session", 63 + "component", "token/servicetoken", 64 + "did", did, 65 + "error", delErr) 66 + } 67 + 51 68 return "", fmt.Errorf("failed to get OAuth session: %w", err) 52 69 } 53 70 ··· 74 91 if err != nil { 75 92 // Auth error - may indicate expired tokens or corrupted session 76 93 InvalidateServiceToken(did, holdDID) 94 + slog.Error("OAuth authentication failed during service token request", 95 + "component", "token/servicetoken", 96 + "did", did, 97 + "holdDID", holdDID, 98 + "pdsEndpoint", pdsEndpoint, 99 + "url", serviceAuthURL, 100 + "error", err, 101 + "errorType", fmt.Sprintf("%T", err), 102 + "hint", "This likely means the PDS rejected the OAuth session - refresh token may be expired or invalidated") 103 + 104 + // Delete the stale OAuth session to force re-authentication 105 + // This also invalidates the UI session automatically 106 + if delErr := refresher.DeleteSession(ctx, did); delErr != nil { 107 + slog.Warn("Failed to delete stale OAuth session", 108 + "component", "token/servicetoken", 109 + "did", did, 110 + "error", delErr) 111 + } 112 + 77 113 return "", fmt.Errorf("OAuth validation failed: %w", err) 78 114 } 79 115 defer resp.Body.Close() ··· 82 118 // Service auth failed 83 119 bodyBytes, _ := io.ReadAll(resp.Body) 84 120 InvalidateServiceToken(did, holdDID) 121 + slog.Error("Service token request returned non-200 status", 122 + "component", "token/servicetoken", 123 + "did", did, 124 + "holdDID", holdDID, 125 + "pdsEndpoint", pdsEndpoint, 126 + "statusCode", resp.StatusCode, 127 + "responseBody", string(bodyBytes), 128 + "hint", "PDS rejected the service token request - check PDS logs for details") 85 129 return "", fmt.Errorf("service auth failed with status %d: %s", resp.StatusCode, string(bodyBytes)) 86 130 } 87 131