Skip to content

Commit 407eaba

Browse files
authored
feat: add com.atproto.server.getServiceAuth endpoint (#26)
* feat: add com.atproto.server.getServiceAuth endpoint This endpoint is required for video uploads. Clients call it to get a service JWT to authenticate with external services like the video service (did:web:video.bsky.app). The endpoint: - Requires authentication - Takes 'aud' (required) and 'lxm' (optional) query params - Returns a signed service JWT with the requested audience and lxm claims Adds 4 new tests for the endpoint. * chore: add changeset for getServiceAuth endpoint * fix: accept service JWTs for video upload auth The video service (video.bsky.app) calls uploadBlob on the PDS using a service JWT issued by getServiceAuth. The auth middleware now accepts these ES256K-signed service JWTs in addition to HS256 session JWTs. Auth flow: 1. Client gets service JWT via getServiceAuth(aud=PDS, lxm=uploadBlob) 2. Client sends video to video.bsky.app with this token 3. Video service calls uploadBlob on PDS using the same token 4. PDS verifies the service JWT signature and allows the upload Adds verifyServiceJwt() function and integration test for the flow. * refactor: restore keypair caching for service JWT verification The actual fix was wrapping Buffer with Uint8Array, not removing caching. Cloudflare Workers' Buffer polyfill doesn't work correctly with @atproto/crypto's verifySignature() - it needs true Uint8Array instances. Restores caching for better performance while keeping the Uint8Array fix. * refactor: share keypair caching between modules Extract keypair caching to a shared module (keypair.ts) used by both service-auth.ts (for creating service JWTs) and session.ts (for verifying them). This ensures consistent behavior and reduces code duplication. * refactor: consolidate service auth code in service-auth.ts Move keypair caching, verifyServiceJwt, and ServiceJwtPayload from separate modules into service-auth.ts where they logically belong alongside createServiceJwt. * feat: add video embed and missing defs lexicons Add app.bsky.embed.video schema for video post support, along with all dependent defs schemas (embed.defs, actor.defs, feed.defs, graph.defs, notification.defs) that are referenced by other lexicons. * chore: add script to check for missing lexicon refs Scans all lexicon JSON files for external references and verifies that corresponding lexicon files exist. Useful for ensuring all dependencies are satisfied when adding new schemas. * ci: run lexicon ref check in update workflow * fix: convert JSON to lexicon format before validating records Use jsonToLex() to convert incoming JSON records to proper lexicon format before validation. This handles $link -> CID conversion and blob object -> BlobRef conversion, fixing video embed validation.
1 parent 4ccba03 commit 407eaba

17 files changed

Lines changed: 1757 additions & 12 deletions

.changeset/video-upload-auth.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@ascorbic/pds": minor
3+
---
4+
5+
Add `com.atproto.server.getServiceAuth` endpoint for video upload authentication
6+
7+
This endpoint is required for video uploads. Clients call it to get a service JWT to authenticate with external services like the video service (`did:web:video.bsky.app`).

.github/workflows/update-lexicons.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ jobs:
1515
uses: actions/checkout@v5
1616
- name: Update lexicon schemas
1717
run: ./packages/pds/scripts/update-lexicons.sh
18+
- name: Check for missing references
19+
run: ./packages/pds/scripts/check-lexicon-refs.sh
1820
- name: Create Pull Request
1921
uses: peter-evans/create-pull-request@v8
2022
with:
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/bin/bash
2+
#
3+
# Check for missing lexicon references
4+
# This script scans all lexicon JSON files and reports any external refs
5+
# that don't have corresponding lexicon files.
6+
#
7+
8+
set -e
9+
10+
LEXICONS_DIR="$(cd "$(dirname "$0")/../src/lexicons" && pwd)"
11+
12+
echo "Checking lexicon references in: $LEXICONS_DIR"
13+
echo ""
14+
15+
# Extract all external refs (those with a namespace, not just #fragment)
16+
# Format: "app.bsky.foo.bar#baz" -> we need "app.bsky.foo.bar"
17+
refs=$(grep -roh '"ref": "[^#"]*#[^"]*"' "$LEXICONS_DIR"/*.json 2>/dev/null | \
18+
grep -v '^"ref": "#' | \
19+
sed 's/"ref": "\([^#]*\)#.*/\1/' | \
20+
sort -u)
21+
22+
missing=()
23+
24+
for ref in $refs; do
25+
file="$LEXICONS_DIR/${ref}.json"
26+
if [ ! -f "$file" ]; then
27+
missing+=("$ref")
28+
fi
29+
done
30+
31+
if [ ${#missing[@]} -eq 0 ]; then
32+
echo "✓ All lexicon references are satisfied!"
33+
echo ""
34+
exit 0
35+
else
36+
echo "✗ Missing lexicon files for the following refs:"
37+
echo ""
38+
for ref in "${missing[@]}"; do
39+
echo " - $ref"
40+
done
41+
echo ""
42+
echo "Add these to scripts/update-lexicons.sh and run it to fetch them."
43+
exit 1
44+
fi

packages/pds/scripts/update-lexicons.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,18 @@ schemas=(
2323
"app/bsky/feed/like"
2424
"app/bsky/feed/repost"
2525
"app/bsky/feed/threadgate"
26+
"app/bsky/feed/defs"
2627

2728
# Actor schemas
2829
"app/bsky/actor/profile"
30+
"app/bsky/actor/defs"
2931

3032
# Graph schemas
3133
"app/bsky/graph/follow"
3234
"app/bsky/graph/block"
3335
"app/bsky/graph/list"
3436
"app/bsky/graph/listitem"
37+
"app/bsky/graph/defs"
3538

3639
# Richtext schemas
3740
"app/bsky/richtext/facet"
@@ -41,6 +44,11 @@ schemas=(
4144
"app/bsky/embed/external"
4245
"app/bsky/embed/record"
4346
"app/bsky/embed/recordWithMedia"
47+
"app/bsky/embed/video"
48+
"app/bsky/embed/defs"
49+
50+
# Notification schemas (referenced by actor.defs)
51+
"app/bsky/notification/defs"
4452
)
4553

4654
# Fetch each schema

packages/pds/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,13 @@ app.get("/xrpc/com.atproto.server.getAccountStatus", requireAuth, (c) =>
215215
server.getAccountStatus(c, getAccountDO(c.env)),
216216
);
217217

218+
// Service auth - used by clients to get JWTs for external services (video, etc.)
219+
app.get(
220+
"/xrpc/com.atproto.server.getServiceAuth",
221+
requireAuth,
222+
server.getServiceAuth,
223+
);
224+
218225
// Actor preferences (stub - returns empty preferences)
219226
app.get("/xrpc/app.bsky.actor.getPreferences", requireAuth, (c) => {
220227
return c.json({ preferences: [] });

0 commit comments

Comments
 (0)