A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.

fix pagination on crew record check

evan.jarrett.net 482d921c c80b5b29

verified
+56 -39
+56 -39
pkg/auth/hold_remote.go
··· 324 324 } 325 325 326 326 // isCrewMemberNoCache queries XRPC without caching (internal helper) 327 + // Handles pagination to check all crew records, not just the first page 327 328 func (a *RemoteHoldAuthorizer) isCrewMemberNoCache(ctx context.Context, holdDID, userDID string) (bool, error) { 328 329 // Resolve DID to URL 329 330 holdURL := atproto.ResolveHoldURL(holdDID) 330 331 331 - // Build XRPC request URL 332 - // GET /xrpc/com.atproto.repo.listRecords?repo={did}&collection=io.atcr.hold.crew 333 - xrpcURL := fmt.Sprintf("%s%s?repo=%s&collection=%s", 334 - holdURL, atproto.RepoListRecords, url.QueryEscape(holdDID), url.QueryEscape(atproto.CrewCollection)) 332 + // Paginate through all crew records 333 + cursor := "" 334 + for { 335 + // Build XRPC request URL with pagination 336 + // GET /xrpc/com.atproto.repo.listRecords?repo={did}&collection=io.atcr.hold.crew&limit=100 337 + xrpcURL := fmt.Sprintf("%s%s?repo=%s&collection=%s&limit=100", 338 + holdURL, atproto.RepoListRecords, url.QueryEscape(holdDID), url.QueryEscape(atproto.CrewCollection)) 339 + if cursor != "" { 340 + xrpcURL += "&cursor=" + url.QueryEscape(cursor) 341 + } 335 342 336 - req, err := http.NewRequestWithContext(ctx, "GET", xrpcURL, nil) 337 - if err != nil { 338 - return false, err 339 - } 343 + req, err := http.NewRequestWithContext(ctx, "GET", xrpcURL, nil) 344 + if err != nil { 345 + return false, err 346 + } 340 347 341 - resp, err := a.httpClient.Do(req) 342 - if err != nil { 343 - return false, fmt.Errorf("XRPC request failed: %w", err) 344 - } 345 - defer resp.Body.Close() 348 + resp, err := a.httpClient.Do(req) 349 + if err != nil { 350 + return false, fmt.Errorf("XRPC request failed: %w", err) 351 + } 346 352 347 - if resp.StatusCode != http.StatusOK { 348 - body, _ := io.ReadAll(resp.Body) 349 - return false, fmt.Errorf("XRPC request failed: status %d: %s", resp.StatusCode, string(body)) 350 - } 353 + if resp.StatusCode != http.StatusOK { 354 + body, _ := io.ReadAll(resp.Body) 355 + resp.Body.Close() 356 + return false, fmt.Errorf("XRPC request failed: status %d: %s", resp.StatusCode, string(body)) 357 + } 358 + 359 + // Parse response 360 + var xrpcResp struct { 361 + Cursor string `json:"cursor"` 362 + Records []struct { 363 + URI string `json:"uri"` 364 + CID string `json:"cid"` 365 + Value struct { 366 + Type string `json:"$type"` 367 + Member string `json:"member"` 368 + Role string `json:"role"` 369 + Permissions []string `json:"permissions"` 370 + AddedAt string `json:"addedAt"` 371 + } `json:"value"` 372 + } `json:"records"` 373 + } 351 374 352 - // Parse response 353 - var xrpcResp struct { 354 - Records []struct { 355 - URI string `json:"uri"` 356 - CID string `json:"cid"` 357 - Value struct { 358 - Type string `json:"$type"` 359 - Member string `json:"member"` 360 - Role string `json:"role"` 361 - Permissions []string `json:"permissions"` 362 - AddedAt string `json:"addedAt"` 363 - } `json:"value"` 364 - } `json:"records"` 365 - } 375 + if err := json.NewDecoder(resp.Body).Decode(&xrpcResp); err != nil { 376 + resp.Body.Close() 377 + return false, fmt.Errorf("failed to decode XRPC response: %w", err) 378 + } 379 + resp.Body.Close() 366 380 367 - if err := json.NewDecoder(resp.Body).Decode(&xrpcResp); err != nil { 368 - return false, fmt.Errorf("failed to decode XRPC response: %w", err) 369 - } 381 + // Check if userDID is in this page of crew records 382 + for _, record := range xrpcResp.Records { 383 + if record.Value.Member == userDID { 384 + // TODO: Check expiration if set 385 + return true, nil 386 + } 387 + } 370 388 371 - // Check if userDID is in the crew list 372 - for _, record := range xrpcResp.Records { 373 - if record.Value.Member == userDID { 374 - // TODO: Check expiration if set 375 - return true, nil 389 + // Check if there are more pages 390 + if xrpcResp.Cursor == "" || len(xrpcResp.Records) == 0 { 391 + break 376 392 } 393 + cursor = xrpcResp.Cursor 377 394 } 378 395 379 396 return false, nil