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.

fix scope mismatch?

+31 -30
-6
lexicons/io/atcr/authFullApp.json
··· 14 14 "resource": "repo", 15 15 "action": ["create", "update", "delete"], 16 16 "collection": ["io.atcr.manifest", "io.atcr.tag", "io.atcr.sailor.star", "io.atcr.sailor.profile", "io.atcr.repo.page"] 17 - }, 18 - { 19 - "type": "permission", 20 - "resource": "rpc", 21 - "lxm": ["com.atproto.repo.getRecord"], 22 - "aud": "*" 23 17 } 24 18 ] 25 19 }
+31 -24
pkg/auth/oauth/client.go
··· 82 82 // See lexicons/io/atcr/authFullApp.json for definition 83 83 // Uses "include:" prefix per ATProto permission spec 84 84 "include:io.atcr.authFullApp", 85 - // Individual repo/rpc scopes (for current PDS compatibility) 86 - // fmt.Sprintf("repo:%s", atproto.ManifestCollection), 87 - // fmt.Sprintf("repo:%s", atproto.TagCollection), 88 - // fmt.Sprintf("repo:%s", atproto.StarCollection), 89 - // fmt.Sprintf("repo:%s", atproto.SailorProfileCollection), 90 - // fmt.Sprintf("repo:%s", atproto.RepoPageCollection), 91 - // "rpc:com.atproto.repo.getRecord?aud=*", 85 + // com.atproto scopes must be separate (permission-sets are namespace-limited) 86 + "rpc:com.atproto.repo.getRecord?aud=*", 92 87 // Blob scopes (not supported in Lexicon permission-sets) 93 88 // Image manifest types (single-arch) 94 89 "blob:application/vnd.oci.image.manifest.v1+json", ··· 228 223 // The session's PersistSessionCallback will save nonce updates to DB 229 224 err = fn(session) 230 225 226 + // If request failed with auth error, delete session to force re-auth 227 + if err != nil && isAuthError(err) { 228 + slog.Warn("Auth error detected, deleting session to force re-auth", 229 + "component", "oauth/refresher", 230 + "did", did, 231 + "error", err) 232 + // Don't hold the lock while deleting - release first 233 + mutex.Unlock() 234 + _ = r.DeleteSession(ctx, did) 235 + mutex.Lock() // Re-acquire for the deferred unlock 236 + } 237 + 231 238 slog.Debug("Released session lock for DoWithSession", 232 239 "component", "oauth/refresher", 233 240 "did", did, ··· 236 243 return err 237 244 } 238 245 246 + // isAuthError checks if an error looks like an OAuth/auth failure 247 + func isAuthError(err error) bool { 248 + if err == nil { 249 + return false 250 + } 251 + errStr := strings.ToLower(err.Error()) 252 + return strings.Contains(errStr, "unauthorized") || 253 + strings.Contains(errStr, "invalid_token") || 254 + strings.Contains(errStr, "insufficient_scope") || 255 + strings.Contains(errStr, "token expired") || 256 + strings.Contains(errStr, "401") 257 + } 258 + 239 259 // resumeSession loads a session from storage 240 260 func (r *Refresher) resumeSession(ctx context.Context, did string) (*oauth.ClientSession, error) { 241 261 // Parse DID ··· 260 280 return nil, fmt.Errorf("no session found for DID: %s", did) 261 281 } 262 282 263 - // Validate that session scopes match current desired scopes 283 + // Log scope differences for debugging, but don't delete session 284 + // The PDS will reject requests if scopes are insufficient 285 + // (Permission-sets get expanded by PDS, so exact matching doesn't work) 264 286 desiredScopes := r.clientApp.Config.Scopes 265 287 if !ScopesMatch(sessionData.Scopes, desiredScopes) { 266 - slog.Debug("Scope mismatch, deleting session", 288 + slog.Debug("Session scopes differ from desired (may be permission-set expansion)", 267 289 "did", did, 268 290 "storedScopes", sessionData.Scopes, 269 291 "desiredScopes", desiredScopes) 270 - 271 - // Delete the session from database since scopes have changed 272 - if err := r.clientApp.Store.DeleteSession(ctx, accountDID, sessionID); err != nil { 273 - slog.Warn("Failed to delete session with mismatched scopes", "error", err, "did", did) 274 - } 275 - 276 - // Also invalidate UI sessions since OAuth is now invalid 277 - if r.uiSessionStore != nil { 278 - r.uiSessionStore.DeleteByDID(did) 279 - slog.Info("Invalidated UI sessions due to scope mismatch", 280 - "component", "oauth/refresher", 281 - "did", did) 282 - } 283 - 284 - return nil, fmt.Errorf("OAuth scopes changed, re-authentication required") 285 292 } 286 293 287 294 // Resume session