mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-03 08:46:57 +00:00
fix error messages for outdated self hosted PDS. fix profile not being creatd on login
This commit is contained in:
+13
-8
@@ -135,6 +135,7 @@ func serveRegistry(cmd *cobra.Command, args []string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create OAuth app: %w", err)
|
||||
}
|
||||
fmt.Println("Using full OAuth scopes (including blob: scope)")
|
||||
|
||||
// 5. Create refresher
|
||||
refresher := oauth.NewRefresher(oauthApp)
|
||||
@@ -160,7 +161,15 @@ func serveRegistry(cmd *cobra.Command, args []string) error {
|
||||
// Connect database for user avatar management
|
||||
oauthServer.SetDatabase(uiDatabase)
|
||||
|
||||
// 8. Initialize auth keys and create token issuer
|
||||
// 8.5. Extract default hold endpoint and set it on OAuth server
|
||||
// This is used to create sailor profiles on first login
|
||||
defaultHoldEndpoint := extractDefaultHoldEndpoint(config)
|
||||
if defaultHoldEndpoint != "" {
|
||||
oauthServer.SetDefaultHoldEndpoint(defaultHoldEndpoint)
|
||||
fmt.Printf("OAuth server will create profiles with default hold: %s\n", defaultHoldEndpoint)
|
||||
}
|
||||
|
||||
// 9. Initialize auth keys and create token issuer
|
||||
var issuer *token.Issuer
|
||||
if config.Auth["token"] != nil {
|
||||
if err := initializeAuthKeys(config); err != nil {
|
||||
@@ -203,11 +212,9 @@ func serveRegistry(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// OAuth client metadata endpoint
|
||||
mux.HandleFunc("/client-metadata.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
// Get the client config from the OAuth app
|
||||
config := oauth.NewClientConfig(baseURL)
|
||||
metadata := config.ClientMetadata()
|
||||
|
||||
// Serve as JSON
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
if err := json.NewEncoder(w).Encode(metadata); err != nil {
|
||||
@@ -219,10 +226,8 @@ func serveRegistry(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// Mount auth endpoints if enabled
|
||||
if issuer != nil {
|
||||
// Extract default hold endpoint from middleware config
|
||||
defaultHoldEndpoint := extractDefaultHoldEndpoint(config)
|
||||
|
||||
// Basic Auth token endpoint (supports device secrets and app passwords)
|
||||
// Reuse defaultHoldEndpoint extracted earlier
|
||||
tokenHandler := token.NewHandler(issuer, deviceStore, defaultHoldEndpoint)
|
||||
tokenHandler.RegisterRoutes(mux)
|
||||
|
||||
@@ -600,14 +605,14 @@ func initializeUIRoutes(database *sql.DB, readOnlyDB *sql.DB, sessionStore *db.S
|
||||
SessionStore: sessionStore,
|
||||
}).Methods("DELETE")
|
||||
|
||||
// Logout endpoint
|
||||
// Logout endpoint (supports both GET and POST)
|
||||
router.HandleFunc("/auth/logout", func(w http.ResponseWriter, r *http.Request) {
|
||||
if sessionID, ok := db.GetSessionID(r); ok {
|
||||
sessionStore.Delete(sessionID)
|
||||
}
|
||||
db.ClearCookie(w)
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
}).Methods("POST")
|
||||
}).Methods("GET", "POST")
|
||||
|
||||
// Start Jetstream worker
|
||||
jetstreamURL := os.Getenv("JETSTREAM_URL")
|
||||
|
||||
@@ -43,13 +43,21 @@ func (h *SettingsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Fetch sailor profile
|
||||
profile, err := atproto.GetProfile(r.Context(), client)
|
||||
if err != nil {
|
||||
// Log error but don't fail - profile might not exist yet
|
||||
fmt.Printf("WARNING [settings]: Failed to fetch profile for %s: %v\n", user.DID, err)
|
||||
profile = &atproto.SailorProfileRecord{}
|
||||
} else {
|
||||
fmt.Printf("DEBUG [settings]: Fetched profile for %s: defaultHold=%s\n", user.DID, profile.DefaultHold)
|
||||
// Error fetching profile - log out user
|
||||
fmt.Printf("WARNING [settings]: Failed to fetch profile for %s: %v - logging out\n", user.DID, err)
|
||||
http.Redirect(w, r, "/auth/logout", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
if profile == nil {
|
||||
// Profile doesn't exist yet (404) - user needs to log out and back in to create it
|
||||
fmt.Printf("WARNING [settings]: Profile doesn't exist for %s - logging out\n", user.DID)
|
||||
http.Redirect(w, r, "/auth/logout", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("DEBUG [settings]: Fetched profile for %s: defaultHold=%s\n", user.DID, profile.DefaultHold)
|
||||
|
||||
data := struct {
|
||||
PageData
|
||||
Profile struct {
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
const (
|
||||
// maxChunkSize is the maximum buffer size before flushing to hold service
|
||||
// Matches S3's minimum multipart upload size
|
||||
maxChunkSize = 5 * 1024 * 1024 // 5MB
|
||||
maxChunkSize = 10 * 1024 * 1024 // 10MB
|
||||
)
|
||||
|
||||
// Global upload tracking (shared across all ProxyBlobStore instances)
|
||||
@@ -242,7 +242,7 @@ func (p *ProxyBlobStore) Create(ctx context.Context, options ...distribution.Blo
|
||||
uploadID: uploadID,
|
||||
parts: make([]CompletedPart, 0),
|
||||
partNumber: 1,
|
||||
buffer: bytes.NewBuffer(make([]byte, 0, maxChunkSize)), // 5MB buffer
|
||||
buffer: bytes.NewBuffer(make([]byte, 0, maxChunkSize)),
|
||||
id: writerID,
|
||||
startedAt: time.Now(),
|
||||
}
|
||||
@@ -527,7 +527,7 @@ func (w *ProxyBlobWriter) Write(p []byte) (int, error) {
|
||||
n, err := w.buffer.Write(p)
|
||||
w.size += int64(n)
|
||||
|
||||
// Flush if buffer reaches 5MB (S3 minimum part size)
|
||||
// Flush if buffer reaches limit (S3 part size)
|
||||
if w.buffer.Len() >= maxChunkSize {
|
||||
if err := w.flushPart(); err != nil {
|
||||
return n, err
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"atcr.io/pkg/appview/db"
|
||||
@@ -25,10 +26,11 @@ type UserStore interface {
|
||||
|
||||
// Server handles OAuth authorization for the AppView
|
||||
type Server struct {
|
||||
app *App
|
||||
refresher *Refresher
|
||||
uiSessionStore UISessionStore
|
||||
db *sql.DB
|
||||
app *App
|
||||
refresher *Refresher
|
||||
uiSessionStore UISessionStore
|
||||
db *sql.DB
|
||||
defaultHoldEndpoint string
|
||||
}
|
||||
|
||||
// NewServer creates a new OAuth server
|
||||
@@ -38,6 +40,11 @@ func NewServer(app *App) *Server {
|
||||
}
|
||||
}
|
||||
|
||||
// SetDefaultHoldEndpoint sets the default hold endpoint for profile creation
|
||||
func (s *Server) SetDefaultHoldEndpoint(endpoint string) {
|
||||
s.defaultHoldEndpoint = endpoint
|
||||
}
|
||||
|
||||
// SetRefresher sets the refresher for invalidating session cache
|
||||
func (s *Server) SetRefresher(refresher *Refresher) {
|
||||
s.refresher = refresher
|
||||
@@ -73,6 +80,14 @@ func (s *Server) ServeAuthorize(w http.ResponseWriter, r *http.Request) {
|
||||
authURL, err := s.app.StartAuthFlow(r.Context(), handle)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR [oauth/server]: Failed to start auth flow: %v\n", err)
|
||||
|
||||
// Check if error is about invalid_client_metadata (usually means PDS doesn't support required scopes)
|
||||
errMsg := err.Error()
|
||||
if strings.Contains(errMsg, "invalid_client_metadata") {
|
||||
s.renderError(w, "OAuth authorization failed: Your PDS does not support one or more required OAuth scopes (likely the 'blob:' scope). Please update your PDS to the latest version and try again.")
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, fmt.Sprintf("failed to start auth flow: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -252,6 +267,12 @@ 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)
|
||||
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
|
||||
}
|
||||
|
||||
// Fetch user's profile record from PDS (contains blob references)
|
||||
profileRecord, err := client.GetProfileRecord(ctx, did)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user