A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
81
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