# Hold Crew Access Control ## Overview ATCR uses crew-based access control for hold (storage) services. Crew records are stored in the **hold's embedded PDS** (not the owner's or user's PDS), making the hold a self-contained ATProto actor with its own access control. ## Current Implementation ### Records in Hold's PDS **Captain record** - Hold ownership (single record at `io.atcr.hold.captain/self`): ```json { "$type": "io.atcr.hold.captain", "owner": "did:plc:alice123", "public": false, "deployedAt": "2025-10-14T...", "region": "iad", "provider": "fly.io" } ``` **Crew records** - Access control (one per member at `io.atcr.hold.crew/{rkey}`): ```json { "$type": "io.atcr.hold.crew", "member": "did:plc:bob456", "role": "admin", "permissions": ["blob:read", "blob:write"], "addedAt": "2025-10-14T..." } ``` ### Authorization Logic Write authorization follows this priority: ``` isAuthorizedWrite(userDID): 1. If userDID == captain.owner → ALLOW 2. If crew record exists for userDID → ALLOW 3. Default → DENY ``` Read authorization depends on `HOLD_PUBLIC` setting: - **Public hold** (`HOLD_PUBLIC=true`): Anonymous + all authenticated users can read - **Private hold** (`HOLD_PUBLIC=false`): Requires crew membership for reads ### Configuration ```bash # Access control environment variables HOLD_PUBLIC=false # Require authentication for reads HOLD_ALLOW_ALL_CREW=false # Only explicit crew members can write ``` ### Crew Management Crew records are managed by the hold captain (owner) using standard ATProto operations on the hold's embedded PDS: **Add crew member:** ```bash # Via hold's PDS (requires captain's OAuth) atproto put-record \ --pds https://hold.example.com \ --collection io.atcr.hold.crew \ --rkey "{memberDID}" \ --value '{ "$type": "io.atcr.hold.crew", "member": "did:plc:bob456", "role": "admin", "permissions": ["blob:read", "blob:write"], "addedAt": "2025-10-14T12:00:00Z" }' ``` **Remove crew member:** ```bash atproto delete-record \ --pds https://hold.example.com \ --collection io.atcr.hold.crew \ --rkey "{memberDID}" ``` **List crew members:** ```bash # Via XRPC GET https://hold.example.com/xrpc/com.atproto.repo.listRecords?repo={holdDID}&collection=io.atcr.hold.crew ``` ## Authentication Flow ``` 1. User pushes image to atcr.io/alice/myapp 2. AppView gets service token from alice's PDS: GET /xrpc/com.atproto.server.getServiceAuth?aud={holdDID} Response: { "token": "..." } 3. AppView calls hold with service token: POST /xrpc/io.atcr.hold.initiateUpload Authorization: Bearer {serviceToken} 4. Hold validates service token: - Checks token is from alice's PDS - Extracts alice's DID from token 5. Hold checks crew membership: - Queries its own PDS: com.atproto.repo.getRecord - Collection: io.atcr.hold.crew - Record key: alice's DID 6. If crew record found → allow upload Else → deny with 403 Forbidden ``` **Trust model:** "Trust but verify" - User OAuth'd to AppView (proves identity) - Service token from user's PDS (proves AppView is acting on behalf of user) - Crew record in hold's PDS (proves user has access to this hold) ## Use Cases ### 1. Personal Hold (Private) ```bash # Owner only HOLD_PUBLIC=false HOLD_ALLOW_ALL_CREW=false # No additional crew records needed - captain has implicit access ``` ### 2. Team Hold (Shared) ```bash # Multiple team members HOLD_PUBLIC=false HOLD_ALLOW_ALL_CREW=false # Captain adds crew members: # - did:plc:alice (admin) # - did:plc:bob (member) # - did:plc:charlie (member) ``` ### 3. Public Hold (Community) ```bash # Allow any authenticated user (TODO: Implement HOLD_ALLOW_ALL_CREW) HOLD_PUBLIC=true HOLD_ALLOW_ALL_CREW=true ``` ## Planned Features ### Pattern-Based Access Control **Status:** Planned but not yet implemented. **Concept:** Allow crew records with pattern matching instead of explicit DIDs: ```json { "$type": "io.atcr.hold.crew", "memberPattern": "*.example.com", "role": "write" } ``` **Use cases:** - `"*"` - Allow all authenticated users - `"*.company.com"` - Allow all users from company domain - `"*.community.social"` - Allow all community members **Implementation needed:** - Add `memberPattern` field to crew record schema (make `member` optional) - Add handle resolution (DID → handle lookup) - Add pattern matching logic - Update authorization to check patterns ### Barred List (Access Revocation) **Status:** Planned but not yet implemented. **Concept:** Explicit deny list that overrides crew membership: ```json { "$type": "io.atcr.hold.crew.barred", "member": "did:plc:former-employee", "reason": "No longer with company", "barredAt": "2025-10-13T12:00:00Z" } ``` **Priority:** Barred list checked before crew list. ### HOLD_ALLOW_ALL_CREW **Status:** Environment variable exists but full implementation pending. **Concept:** Automatically create/manage wildcard crew record via env var: ```bash HOLD_ALLOW_ALL_CREW=true # Creates crew record with memberPattern: "*" ``` **Implementation needed:** - Auto-create wildcard crew record on startup if env=true - Auto-delete wildcard crew record if env changes to false - Use well-known rkey "allow-all" for managed record ## Architecture Notes ### Why Hold's Embedded PDS? **Key insight:** Crew records are **shared data** about the hold, not user-specific data. **Benefits:** - **Self-contained**: Hold is independent ATProto actor - **Portable**: Hold can move without coordinating with user PDSs - **Discoverable**: Query hold's PDS to see who has access - **Standard**: Uses normal ATProto sync endpoints (subscribeRepos, getRecord, listRecords) **Comparison:** - **User's PDS**: Stores user-specific data (manifests, sailor profile) - **Hold's PDS**: Stores hold-specific data (captain, crew, configuration) - Clear separation of concerns ### Security Considerations 1. **Public Records**: Crew records are public (anyone can see who has access to a hold) 2. **Service Tokens**: Hold trusts user's PDS to issue valid service tokens 3. **DID-Based**: Crew membership is DID-based (permanent), not handle-based 4. **Captain Control**: Only captain can modify crew records (via OAuth to hold's PDS) ## Future Improvements 1. **Crew management UI** - Web interface for adding/removing crew members 2. **Pattern-based matching** - Implement `memberPattern` field 3. **Barred list** - Implement access revocation 4. **Role-based permissions** - Fine-grained permissions beyond read/write 5. **Temporary access** - Time-limited crew membership (`expiresAt` field) 6. **Audit logging** - Track access grants/denials ## References - [EMBEDDED_PDS.md](./EMBEDDED_PDS.md) - Embedded PDS architecture details - [BYOS.md](./BYOS.md) - BYOS deployment and usage - [ATProto Lexicon Spec](https://atproto.com/specs/lexicon)