A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go

fix error code checking to not just check the raw string response in the case that '401' shows up in the sha256

evan.jarrett.net 7c064ba8 136c0a0e

verified
+52 -11
+2 -1
deploy/upcloud/configs/cloudinit.sh.tmpl
··· 17 # System packages 18 export DEBIAN_FRONTEND=noninteractive 19 apt-get update && apt-get upgrade -y 20 - apt-get install -y git gcc make curl libsqlite3-dev nodejs npm htop 21 22 # Swap (for small instances) 23 if [ ! -f /swapfile ]; then
··· 17 # System packages 18 export DEBIAN_FRONTEND=noninteractive 19 apt-get update && apt-get upgrade -y 20 + apt-get install -y git gcc make curl libsqlite3-dev nodejs npm htop systemd-timesyncd 21 + timedatectl set-ntp true 22 23 # Swap (for small instances) 24 if [ ! -f /swapfile ]; then
+25 -6
pkg/appview/handlers/oauth_errors.go
··· 2 3 import ( 4 "context" 5 "log/slog" 6 "strings" 7 8 "atcr.io/pkg/auth/oauth" 9 ) 10 11 // isOAuthError checks if an error indicates OAuth authentication failure 12 // These errors indicate the OAuth session is invalid and should be cleaned up 13 func isOAuthError(err error) bool { 14 if err == nil { 15 return false 16 } 17 errStr := strings.ToLower(err.Error()) 18 - return strings.Contains(errStr, "401") || 19 - strings.Contains(errStr, "403") || 20 - strings.Contains(errStr, "invalid_token") || 21 strings.Contains(errStr, "invalid_grant") || 22 strings.Contains(errStr, "use_dpop_nonce") || 23 - strings.Contains(errStr, "unauthorized") || 24 - strings.Contains(errStr, "token") && strings.Contains(errStr, "expired") || 25 - strings.Contains(errStr, "authentication failed") 26 } 27 28 // handleOAuthError checks if an error is OAuth-related and invalidates UI sessions if so
··· 2 3 import ( 4 "context" 5 + "errors" 6 "log/slog" 7 "strings" 8 9 "atcr.io/pkg/auth/oauth" 10 + "github.com/bluesky-social/indigo/atproto/atclient" 11 + "github.com/bluesky-social/indigo/xrpc" 12 ) 13 14 // isOAuthError checks if an error indicates OAuth authentication failure 15 // These errors indicate the OAuth session is invalid and should be cleaned up 16 + // Uses structured error types to avoid false positives from substring matching 17 func isOAuthError(err error) bool { 18 if err == nil { 19 return false 20 } 21 + 22 + // Check structured error types first 23 + var xrpcErr *xrpc.Error 24 + if errors.As(err, &xrpcErr) && (xrpcErr.StatusCode == 401 || xrpcErr.StatusCode == 403) { 25 + return true 26 + } 27 + var apiErr *atclient.APIError 28 + if errors.As(err, &apiErr) { 29 + if apiErr.StatusCode == 401 || apiErr.StatusCode == 403 { 30 + return true 31 + } 32 + if apiErr.Name == "InvalidToken" || apiErr.Name == "InsufficientScope" || apiErr.Name == "InvalidGrant" { 33 + return true 34 + } 35 + } 36 + 37 + // Fallback: check for known auth-specific error strings that won't 38 + // appear in digests or URIs 39 errStr := strings.ToLower(err.Error()) 40 + return strings.Contains(errStr, "invalid_token") || 41 strings.Contains(errStr, "invalid_grant") || 42 strings.Contains(errStr, "use_dpop_nonce") || 43 + strings.Contains(errStr, "authentication failed") || 44 + strings.Contains(errStr, "token expired") 45 } 46 47 // handleOAuthError checks if an error is OAuth-related and invalidates UI sessions if so
+25 -4
pkg/auth/oauth/client.go
··· 6 7 import ( 8 "context" 9 "fmt" 10 "log/slog" 11 "strings" ··· 13 "time" 14 15 "atcr.io/pkg/atproto" 16 "github.com/bluesky-social/indigo/atproto/atcrypto" 17 "github.com/bluesky-social/indigo/atproto/auth/oauth" 18 "github.com/bluesky-social/indigo/atproto/syntax" 19 ) 20 21 // permissionSetExpansions maps lexicon IDs to their expanded scope format. ··· 312 } 313 314 // isAuthError checks if an error looks like an OAuth/auth failure 315 func isAuthError(err error) bool { 316 if err == nil { 317 return false 318 } 319 errStr := strings.ToLower(err.Error()) 320 - return strings.Contains(errStr, "unauthorized") || 321 - strings.Contains(errStr, "invalid_token") || 322 strings.Contains(errStr, "insufficient_scope") || 323 - strings.Contains(errStr, "token expired") || 324 - strings.Contains(errStr, "401") 325 } 326 327 // resumeSession loads a session from storage
··· 6 7 import ( 8 "context" 9 + "errors" 10 "fmt" 11 "log/slog" 12 "strings" ··· 14 "time" 15 16 "atcr.io/pkg/atproto" 17 + "github.com/bluesky-social/indigo/atproto/atclient" 18 "github.com/bluesky-social/indigo/atproto/atcrypto" 19 "github.com/bluesky-social/indigo/atproto/auth/oauth" 20 "github.com/bluesky-social/indigo/atproto/syntax" 21 + "github.com/bluesky-social/indigo/xrpc" 22 ) 23 24 // permissionSetExpansions maps lexicon IDs to their expanded scope format. ··· 315 } 316 317 // isAuthError checks if an error looks like an OAuth/auth failure 318 + // Uses structured error types to avoid false positives from substring matching 319 + // (e.g., a digest hash containing "401" in a RecordNotFound error) 320 func isAuthError(err error) bool { 321 if err == nil { 322 return false 323 } 324 + 325 + // Check structured error types first 326 + var xrpcErr *xrpc.Error 327 + if errors.As(err, &xrpcErr) && xrpcErr.StatusCode == 401 { 328 + return true 329 + } 330 + var apiErr *atclient.APIError 331 + if errors.As(err, &apiErr) { 332 + if apiErr.StatusCode == 401 { 333 + return true 334 + } 335 + if apiErr.Name == "InvalidToken" || apiErr.Name == "InsufficientScope" { 336 + return true 337 + } 338 + } 339 + 340 + // Fallback: check for known auth-specific error strings that won't 341 + // appear in digests or URIs 342 errStr := strings.ToLower(err.Error()) 343 + return strings.Contains(errStr, "invalid_token") || 344 strings.Contains(errStr, "insufficient_scope") || 345 + strings.Contains(errStr, "token expired") 346 } 347 348 // resumeSession loads a session from storage