diff --git a/cmd/appview/serve.go b/cmd/appview/serve.go index 9a95ee2..abb3685 100644 --- a/cmd/appview/serve.go +++ b/cmd/appview/serve.go @@ -469,11 +469,7 @@ func initializeUIRoutes(database *sql.DB, readOnlyDB *sql.DB, sessionStore *db.S Templates: templates, }).Methods("GET") - router.Handle("/auth/oauth/login", &uihandlers.LoginSubmitHandler{ - Refresher: refresher, - Directory: oauthApp.Directory(), - SessionStore: sessionStore, - }).Methods("POST") + router.Handle("/auth/oauth/login", &uihandlers.LoginSubmitHandler{}).Methods("POST") // Public routes (with optional auth for navbar) // SECURITY: Public pages use read-only DB diff --git a/pkg/appview/handlers/auth.go b/pkg/appview/handlers/auth.go index e9ba923..3a32dc1 100644 --- a/pkg/appview/handlers/auth.go +++ b/pkg/appview/handlers/auth.go @@ -1,14 +1,8 @@ package handlers import ( - "fmt" "html/template" "net/http" - "time" - - "atcr.io/pkg/auth/oauth" - "github.com/bluesky-social/indigo/atproto/identity" - "github.com/bluesky-social/indigo/atproto/syntax" ) // LoginHandler shows the OAuth login form @@ -38,14 +32,6 @@ func (h *LoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // LoginSubmitHandler processes the login form submission type LoginSubmitHandler struct { - Refresher *oauth.Refresher - Directory identity.Directory - SessionStore UISessionStore -} - -// UISessionStore is the interface for UI session management -type UISessionStore interface { - CreateWithOAuth(did, handle, pdsEndpoint, oauthSessionID string, duration time.Duration) (string, error) } func (h *LoginSubmitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -65,71 +51,6 @@ func (h *LoginSubmitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - // Attempt silent login first - if h.Refresher != nil && h.Directory != nil && h.SessionStore != nil { - // Parse handle - handleSyntax, err := syntax.ParseHandle(handle) - if err == nil { - // Resolve handle to identity (DID + PDS endpoint) - ident, err := h.Directory.LookupHandle(r.Context(), handleSyntax) - if err == nil { - did := ident.DID.String() - - // Try to get existing OAuth session - session, err := h.Refresher.GetSession(r.Context(), did) - if err == nil { - // Check if the session has all required scopes - requiredScopes := oauth.GetDefaultScopes() - sessionScopes := session.Data.Scopes - - if !hasAllScopes(sessionScopes, requiredScopes) { - fmt.Printf("DEBUG [auth]: Session scopes mismatch for %s. Required: %v, Have: %v. Forcing re-auth.\n", - handle, requiredScopes, sessionScopes) - } else { - // Found valid OAuth session with all required scopes! Create UI session silently - fmt.Printf("DEBUG [auth]: Silent login successful for %s (DID: %s)\n", handle, did) - - // Get PDS endpoint from identity - pdsEndpoint := ident.PDSEndpoint() - - // Get OAuth sessionID from refresher - sessionID := h.Refresher.GetSessionID(did) - - uiSessionID, err := h.SessionStore.CreateWithOAuth(did, handle, pdsEndpoint, sessionID, 30*24*time.Hour) - if err == nil { - // Set session cookie - http.SetCookie(w, &http.Cookie{ - Name: "atcr_session", - Value: uiSessionID, - Path: "/", - MaxAge: 30 * 86400, // 30 days - HttpOnly: true, - Secure: true, - SameSite: http.SameSiteLaxMode, - }) - - // Redirect to return URL - fmt.Printf("DEBUG [auth]: Silent login complete, redirecting to %s\n", returnTo) - http.Redirect(w, r, returnTo, http.StatusFound) - return - } - - fmt.Printf("WARNING [auth]: Failed to create UI session during silent login: %v\n", err) - } - } else { - fmt.Printf("DEBUG [auth]: No valid OAuth session found for %s: %v\n", handle, err) - } - } else { - fmt.Printf("DEBUG [auth]: Failed to resolve handle %s: %v\n", handle, err) - } - } else { - fmt.Printf("DEBUG [auth]: Failed to parse handle %s: %v\n", handle, err) - } - } - - // Silent login failed or not configured - proceed with full OAuth flow - fmt.Printf("DEBUG [auth]: Proceeding with full OAuth flow for %s\n", handle) - // Store return_to in cookie so callback can use it http.SetCookie(w, &http.Cookie{ Name: "oauth_return_to", @@ -144,19 +65,3 @@ func (h *LoginSubmitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Redirect to OAuth authorize with handle http.Redirect(w, r, "/auth/oauth/authorize?handle="+handle, http.StatusFound) } - -// hasAllScopes checks if grantedScopes contains all requiredScopes -func hasAllScopes(grantedScopes, requiredScopes []string) bool { - grantedSet := make(map[string]bool) - for _, scope := range grantedScopes { - grantedSet[scope] = true - } - - for _, required := range requiredScopes { - if !grantedSet[required] { - return false - } - } - - return true -} diff --git a/pkg/atproto/profile.go b/pkg/atproto/profile.go index 0d98a99..2b9e3f0 100644 --- a/pkg/atproto/profile.go +++ b/pkg/atproto/profile.go @@ -11,7 +11,7 @@ const ProfileRKey = "self" // EnsureProfile checks if a user's profile exists and creates it if needed // This should be called during authentication (OAuth exchange or token service) -// If defaultHoldEndpoint is provided and profile doesn't exist, creates profile with that default +// If defaultHoldEndpoint is provided, creates profile with that default (or empty if not provided) func EnsureProfile(ctx context.Context, client *Client, defaultHoldEndpoint string) error { // Check if profile already exists profile, err := client.GetRecord(ctx, SailorProfileCollection, ProfileRKey) @@ -20,14 +20,8 @@ func EnsureProfile(ctx context.Context, client *Client, defaultHoldEndpoint stri return nil } - // Profile doesn't exist - // Only create if we have a default hold endpoint to set - if defaultHoldEndpoint == "" { - // No default configured, don't create empty profile - return nil - } - - // Create new profile with default hold + // Profile doesn't exist - create it + // defaultHoldEndpoint can be empty string (user will need to configure it later) newProfile := NewSailorProfileRecord(defaultHoldEndpoint) _, err = client.PutRecord(ctx, SailorProfileCollection, ProfileRKey, newProfile) @@ -35,6 +29,7 @@ func EnsureProfile(ctx context.Context, client *Client, defaultHoldEndpoint stri return fmt.Errorf("failed to create sailor profile: %w", err) } + fmt.Printf("DEBUG [profile]: Created sailor profile with defaultHold=%s\n", defaultHoldEndpoint) return nil } diff --git a/pkg/auth/oauth/server.go b/pkg/auth/oauth/server.go index b07ec4c..5692d78 100644 --- a/pkg/auth/oauth/server.go +++ b/pkg/auth/oauth/server.go @@ -267,10 +267,13 @@ func (s *Server) fetchAndStoreAvatar(ctx context.Context, did, sessionID, handle // Create authenticated atproto client using the indigo session's API client client := atproto.NewClientWithIndigoClient(pdsEndpoint, did, session.APIClient()) - // Ensure sailor profile exists (creates with default hold on first login) + // Ensure sailor profile exists (creates with default hold if configured, or empty profile if not) + fmt.Printf("DEBUG [oauth/server]: Ensuring profile exists for %s (defaultHold=%s)\n", did, s.defaultHoldEndpoint) if err := atproto.EnsureProfile(ctx, client, s.defaultHoldEndpoint); err != nil { fmt.Printf("WARNING [oauth/server]: Failed to ensure profile for %s: %v\n", did, err) - // Continue anyway - profile creation is not critical + // Continue anyway - profile creation is not critical for avatar fetch + } else { + fmt.Printf("DEBUG [oauth/server]: Profile ensured for %s\n", did) } // Fetch user's profile record from PDS (contains blob references)