this repo has no description
1# Scope Validation Comparison: pds.js vs atproto PDS 2 3Comparison of OAuth scope validation between this implementation and the official AT Protocol PDS. 4 5--- 6 7## Scope Types Supported 8 9| Scope Type | Format | pds.js | atproto PDS | 10|------------|--------|--------|-------------| 11| `atproto` | Static | Checked (loose) | Required for all OAuth | 12| `transition:generic` | Static | Not recognized | Full repo/blob bypass | 13| `transition:email` | Static | N/A | Read account email | 14| `transition:chat.bsky` | Static | N/A | Chat RPC access | 15| `repo:<collection>:<action>` | Granular | Not parsed | Full parsing + enforcement | 16| `blob:<mime>` | Granular | Not parsed | Full parsing + enforcement | 17| `rpc:<aud>:<lxm>` | Granular | Not parsed | Full parsing + enforcement | 18 19--- 20 21## Scope Enforcement by Endpoint 22 23### com.atproto.repo.createRecord 24 25| Aspect | pds.js | atproto PDS | 26|--------|--------|-------------| 27| Scope check | `hasRequiredScope(scope, 'atproto')` | `permissions.assertRepo({ action: 'create', collection })` | 28| Required scope | `atproto` anywhere in scope string | `repo:<collection>:create` or `transition:generic` or `atproto` | 29| OAuth-only check | No (checks all tokens) | Yes (legacy Bearer bypasses) | 30| Error response | 403 "Insufficient scope for repo write" | 403 "Missing required scope \"repo:...\"" | 31 32### com.atproto.repo.putRecord 33 34| Aspect | pds.js | atproto PDS | 35|--------|--------|-------------| 36| Scope check | `hasRequiredScope(scope, 'atproto')` | `assertRepo({ action: 'create' })` AND `assertRepo({ action: 'update' })` | 37| Required scope | `atproto` | Both `repo:<collection>:create` AND `repo:<collection>:update` | 38| Notes | Single check | Requires both since putRecord can create or update | 39 40### com.atproto.repo.deleteRecord 41 42| Aspect | pds.js | atproto PDS | 43|--------|--------|-------------| 44| Scope check | `hasRequiredScope(scope, 'atproto')` | `permissions.assertRepo({ action: 'delete', collection })` | 45| Required scope | `atproto` | `repo:<collection>:delete` | 46 47### com.atproto.repo.applyWrites 48 49| Aspect | pds.js | atproto PDS | 50|--------|--------|-------------| 51| Scope check | `hasRequiredScope(scope, 'atproto')` | Iterates all writes, asserts each unique action/collection pair | 52| Required scope | `atproto` | All `repo:<collection>:<action>` for each write | 53| Per-write validation | No | Yes | 54 55### com.atproto.repo.uploadBlob 56 57| Aspect | pds.js | atproto PDS | 58|--------|--------|-------------| 59| Scope check | `hasRequiredScope(scope, 'atproto')` | `permissions.assertBlob({ mime: encoding })` | 60| Required scope | `atproto` | `blob:<mime-type>` (e.g., `blob:image/*`) | 61| MIME type awareness | No | Yes (validates against Content-Type) | 62 63### app.bsky.actor.getPreferences 64 65| Aspect | pds.js | atproto PDS | 66|--------|--------|-------------| 67| Scope check | Requires auth only | `permissions.assertRpc({ aud, lxm })` | 68| Required scope | Any valid auth | `rpc:app.bsky.actor.getPreferences` | 69 70### app.bsky.actor.putPreferences 71 72| Aspect | pds.js | atproto PDS | 73|--------|--------|-------------| 74| Scope check | Requires auth only | `permissions.assertRpc({ aud, lxm })` | 75| Required scope | Any valid auth | `rpc:app.bsky.actor.putPreferences` | 76 77--- 78 79## Scope Parsing 80 81| Feature | pds.js | atproto PDS | 82|---------|--------|-------------| 83| Scope string splitting | `scope.split(' ')` | `ScopesSet` class | 84| Repo scope parsing | None | `RepoPermission.fromString()` | 85| Blob scope parsing | None | `BlobPermission.fromString()` | 86| RPC scope parsing | None | `RpcPermission.fromString()` | 87| Scope validation | None (accepts any string) | Validates syntax, ignores invalid | 88| Scope normalization | None | Sorts, dedupes, simplifies wildcards | 89 90--- 91 92## Permission Checking 93 94| Feature | pds.js | atproto PDS | 95|---------|--------|-------------| 96| Permission class | None | `ScopePermissions` / `ScopePermissionsTransition` | 97| `allowsRepo(collection, action)` | N/A | Yes | 98| `allowsBlob(mime)` | N/A | Yes (with MIME wildcard matching) | 99| `allowsRpc(aud, lxm)` | N/A | Yes | 100| Transition scope handling | None | `transition:generic` bypasses repo/blob checks | 101| Error messages | Generic | Specific missing scope in error | 102 103--- 104 105## OAuth Flow 106 107| Feature | pds.js | atproto PDS | 108|---------|--------|-------------| 109| `scopes_supported` in metadata | `['atproto']` | `['atproto']` (but accepts granular) | 110| Scope validation at PAR | None | Validates syntax | 111| Scope stored in token | Yes | Yes | 112| Scope returned in token response | Yes | Yes | 113| `atproto` scope required | Checked at endpoints | Required at token verification | 114 115--- 116 117## Transition Scope Behavior (atproto PDS) 118 119| Scope | Effect | 120|-------|--------| 121| `transition:generic` | Bypasses ALL repo permission checks | 122| `transition:generic` | Bypasses ALL blob permission checks | 123| `transition:generic` | Allows all RPC except `chat.bsky.*` | 124| `transition:chat.bsky` | Allows `chat.bsky.*` RPC methods | 125| `transition:email` | Allows `account:email:read` | 126 127pds.js does not recognize any transition scopes. 128 129--- 130 131## Summary 132 133| Category | pds.js | atproto PDS | 134|----------|--------|-------------| 135| Scope parsing | String contains check | Full parser per scope type | 136| Enforcement granularity | Binary (has atproto or not) | Per-collection, per-action | 137| Transition scope support | None | Full | 138| MIME-aware blob scopes | No | Yes | 139| RPC scopes | No | Yes | 140| Error specificity | Generic 403 | Names missing scope | 141 142--- 143 144## Gaps to Address 145 1461. **Scope parsing** — Need to parse `repo:*:create` and `blob:image/*` syntax 1472. **Permission class** — Need `allowsRepo(collection, action)` and `allowsBlob(mime)` methods 1483. **Transition scopes** — Need `transition:generic` to bypass checks 1494. **Per-endpoint enforcement** — Check specific scope at each write endpoint 1505. **MIME matching**`blob:image/*` should match `image/png`, `image/jpeg`, etc. 1516. **Error messages** — Return which scope is missing, not generic error