A zero-dependency AT Protocol Personal Data Server written in JavaScript
atproto pds
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 | Full access | Required for all OAuth | 12| `transition:generic` | Static | Full access | 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=<action>` | Granular | Full parsing + enforcement | Full parsing + enforcement | 16| `blob:<mime>` | Granular | Full parsing + enforcement | Full parsing + enforcement | 17| `rpc:<aud>:<lxm>` | Granular | Not implemented | 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 | `ScopePermissions.allowsRepo(collection, 'create')` | `permissions.assertRepo({ action: 'create', collection })` | 28| Required scope | `repo:<collection>?action=create` or `transition:generic` or `atproto` | `repo:<collection>?action=create` or `transition:generic` or `atproto` | 29| OAuth-only check | Yes (legacy tokens without scope bypass) | Yes (legacy Bearer bypasses) | 30| Error response | 403 "Missing required scope \"repo:...?action=...\"" | 403 "Missing required scope \"repo:...?action=...\"" | 31 32### com.atproto.repo.putRecord 33 34| Aspect | pds.js | atproto PDS | 35|--------|--------|-------------| 36| Scope check | `allowsRepo(collection, 'create')` AND `allowsRepo(collection, 'update')` | `assertRepo({ action: 'create' })` AND `assertRepo({ action: 'update' })` | 37| Required scope | `repo:<collection>?action=create&action=update` | `repo:<collection>?action=create&action=update` | 38| Notes | Requires both since putRecord can create or update | 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 | `ScopePermissions.allowsRepo(collection, 'delete')` | `permissions.assertRepo({ action: 'delete', collection })` | 45| Required scope | `repo:<collection>?action=delete` | `repo:<collection>?action=delete` | 46 47### com.atproto.repo.applyWrites 48 49| Aspect | pds.js | atproto PDS | 50|--------|--------|-------------| 51| Scope check | Iterates all writes, checks each unique action/collection pair | Iterates all writes, asserts each unique action/collection pair | 52| Required scope | All `repo:<collection>?action=<action>` for each write | All `repo:<collection>?action=<action>` for each write | 53| Per-write validation | Yes | Yes | 54 55### com.atproto.repo.uploadBlob 56 57| Aspect | pds.js | atproto PDS | 58|--------|--------|-------------| 59| Scope check | `ScopePermissions.allowsBlob(contentType)` | `permissions.assertBlob({ mime: encoding })` | 60| Required scope | `blob:<mime-type>` (e.g., `blob:image/*`) | `blob:<mime-type>` (e.g., `blob:image/*`) | 61| MIME type awareness | Yes (validates against Content-Type) | 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 | `parseRepoScope()` | `RepoPermission.fromString()` | 85| Repo scope format | `repo:collection?action=create&action=update` | `repo:collection?action=create&action=update` | 86| Blob scope parsing | `parseBlobScope()` | `BlobPermission.fromString()` | 87| RPC scope parsing | None | `RpcPermission.fromString()` | 88| Scope validation | Returns null for invalid | Validates syntax, ignores invalid | 89| Action deduplication | Yes (via Set) | Yes | 90| Default actions | All (create, update, delete) when no `?action=` | All (create, update, delete) when no `?action=` | 91 92--- 93 94## Permission Checking 95 96| Feature | pds.js | atproto PDS | 97|---------|--------|-------------| 98| Permission class | `ScopePermissions` | `ScopePermissions` / `ScopePermissionsTransition` | 99| `allowsRepo(collection, action)` | Yes | Yes | 100| `allowsBlob(mime)` | Yes (with MIME wildcard matching) | Yes (with MIME wildcard matching) | 101| `allowsRpc(aud, lxm)` | N/A | Yes | 102| Transition scope handling | `transition:generic` bypasses repo/blob checks | `transition:generic` bypasses repo/blob checks | 103| Error messages | Specific missing scope in error | Specific missing scope in error | 104 105--- 106 107## OAuth Flow 108 109| Feature | pds.js | atproto PDS | 110|---------|--------|-------------| 111| `scopes_supported` in metadata | `['atproto']` | `['atproto']` (but accepts granular) | 112| Scope validation at PAR | None | Validates syntax | 113| Scope stored in token | Yes | Yes | 114| Scope returned in token response | Yes | Yes | 115| `atproto` scope required | Checked at endpoints | Required at token verification | 116 117--- 118 119## Transition Scope Behavior 120 121| Scope | pds.js | atproto PDS | 122|-------|--------|-------------| 123| `transition:generic` | Bypasses all repo/blob permission checks | Bypasses ALL repo/blob permission checks | 124| `transition:chat.bsky` | Not implemented | Allows `chat.bsky.*` RPC methods | 125| `transition:email` | Not implemented | Allows `account:email:read` | 126 127--- 128 129## Summary 130 131| Category | pds.js | atproto PDS | 132|----------|--------|-------------| 133| Scope parsing | Full parser for repo/blob | Full parser per scope type | 134| Enforcement granularity | Per-collection, per-action | Per-collection, per-action | 135| Transition scope support | `transition:generic` only | Full | 136| MIME-aware blob scopes | Yes | Yes | 137| RPC scopes | No | Yes | 138| Error specificity | Names missing scope | Names missing scope | 139 140--- 141 142## Remaining Gaps 143 1441. **RPC scopes**`rpc:<aud>:<lxm>` parsing and enforcement not implemented 1452. **Additional transition scopes**`transition:chat.bsky` and `transition:email` not implemented 1463. **Scope validation at PAR** — Could validate scope syntax during authorization request