~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0

A confidential client's OAuth session lasts as on the reference PDS

Merged
Merged chadtmiller.com asked to merge into main from chadtmiller.com/pds.js:oauth/confidential-session

What changes

An OAuth session of a confidential client (private_key_jwt) now ends two years after its first authorization, or after three months without a refresh. A public client's session still ends after two weeks.

Why

Every client's session ended two weeks after its first authorization, however often it was refreshed. The comment said this matched the reference server, but that limit is its one for public clients: @atproto/oauth-provider gives confidential clients SESSION_LIFETIME_EXTENDED (2 years) and REFRESH_LIFETIME_EXTENDED (3 months). A server acting for a user, such as knotless's MCP connector, had to send them to sign in again every two weeks.

The longer limit applies only after authenticateClient has checked the client's assertion on that refresh, so a public client cannot claim it. A row without updatedAt is aged from createdAt.

How it was checked

Three tests in test/oauth-endpoints.test.js refresh with a real signed assertion: past two weeks while in use, retired after three months idle, retired after two years however used. That file and biome pass here, and tsc reports nothing in the changed files. The full gate is left to CI.

Shortcomings

A session that already passed two weeks has had its token deleted, so that client still signs in again once. This applies to a PDS once it upgrades.

🤖 Generated with Claude Code

Approved

This gives private_key_jwt clients a 2-year session limit and a 3-month idle limit on refresh. Public clients keep 2 weeks. The check runs after authenticateClient, so a public client can't claim the longer limits. I found nothing that needs changing.

  • Timestamps: createdAt carries over on rotation and updatedAt advances on each refresh, so the age checks use the right values. The updatedAt ?? createdAt fallback covers old rows.
  • Tests: they cover the three cases with a really signed assertion.
  • Changeset: it is included.

I couldn't read AGENTS.md; the read errored. I read the whole diff.

Merged as 8e68897