Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c076f50
feat: implement deactivated account pattern for migration
claude Dec 30, 2025
649d5f4
fix: improve init wizard UX and handle resolution
ascorbic Dec 31, 2025
a7197a8
feat: add migration progress tracking endpoints
ascorbic Dec 31, 2025
0ab0daa
feat: add migrate command and improve init/deploy workflow
ascorbic Dec 31, 2025
3f62692
chore: add changeset for deactivated account pattern
claude Dec 31, 2025
0529a7e
chore: add changesets for create-pds and oauth-provider
claude Dec 31, 2025
2b16fbc
Update create-pds-ux.md
ascorbic Dec 31, 2025
798d2c9
docs: add comprehensive README for oauth-provider package
claude Dec 31, 2025
a0cb71b
fix: address PR review comments
claude Dec 31, 2025
59085ca
fix: prevent test state leakage and improve import validation
claude Dec 31, 2025
fc537d8
refactor: address Copilot review comments
claude Dec 31, 2025
5386759
refactor: remove backwards compatibility for activated field and add …
claude Dec 31, 2025
8e3ed6b
fix: correct type definition in migration test (activated -> active)
claude Dec 31, 2025
b205ef8
fix: prevent state leakage from resetMigration tests
claude Dec 31, 2025
c30312c
fix: reset repoInitialized flag in resetMigration
ascorbic Dec 31, 2025
fd17bad
feat: add e2e test suite for PDS (#34)
ascorbic Dec 31, 2025
b9c963f
ci: add e2e test workflow
ascorbic Dec 31, 2025
8a89f0c
attempt: disable inspector for e2e tests
claude Jan 1, 2026
fcb4711
revert: remove inspector disable attempts (sandbox-specific issue)
claude Jan 1, 2026
29cafcf
feat: add detailed logging to e2e test setup
claude Jan 1, 2026
05aae34
fix: simplify Vite port detection regex
claude Jan 1, 2026
456fed9
fix: strip ANSI codes before matching Vite port
claude Jan 1, 2026
5a40334
refactor: clean up e2e setup logging
claude Jan 1, 2026
6723c96
fix: address knip unused code warnings
claude Jan 1, 2026
49abafc
chore: don't run e2e in main test file
ascorbic Jan 1, 2026
6dc3485
refactor: use @atproto helpers for record serialization and blob extr…
claude Jan 1, 2026
074e979
fix: changes from review
ascorbic Jan 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/create-pds-ux.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-pds": patch
---

Improve UX with clearer prompts
34 changes: 34 additions & 0 deletions .changeset/deactivated-account-pattern.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
"@ascorbic/pds": minor
---

Implement deactivated account pattern for seamless account migration

**Account State Management:**
- Add account activation state tracking to support migration workflows
- New `INITIAL_ACTIVE` environment variable controls whether accounts start active or deactivated
- Accounts can transition between active and deactivated states

**Migration Endpoints:**
- `POST /xrpc/com.atproto.server.activateAccount` - Enable writes and firehose events
- `POST /xrpc/com.atproto.server.deactivateAccount` - Disable writes while keeping reads available
- Enhanced `getAccountStatus` to return actual activation state and migration metrics

**Write Protection:**
- Write operations (`createRecord`, `putRecord`, `deleteRecord`, `applyWrites`) are blocked when account is deactivated
- Returns clear "AccountDeactivated" error with helpful instructions
- Read operations, `importRepo`, `uploadBlob`, and `activateAccount` remain available

**Improved Setup Flow:**
- `pds init` now asks if you're migrating an existing account
- For migrations: auto-resolves handle to DID, deploys account as deactivated
- For new accounts: generates identity, deploys as active
- Worker name automatically generated from handle using smart slugification

**Migration UX:**
- Handle resolution using DNS-over-HTTPS via `@atproto-labs/handle-resolver`
- Retry logic with helpful error messages for failed handle lookups
- Step-by-step guidance for export, import, PLC update, and activation
- Custom domain validation to prevent using hosted handles (*.bsky.social)

This enables users to safely migrate their Bluesky accounts to self-hosted infrastructure with a clean, resumable workflow.
28 changes: 28 additions & 0 deletions .changeset/oauth-provider-initial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
"@ascorbic/atproto-oauth-provider": minor
---

Initial release of AT Protocol OAuth 2.1 Provider

A complete OAuth 2.1 Authorization Server implementation for AT Protocol, enabling "Login with Bluesky" functionality.

**Features:**
- Full OAuth 2.1 Authorization Code flow with PKCE
- DPoP (Demonstrating Proof of Possession) support for token binding
- PAR (Pushed Authorization Requests) for secure request initiation
- Client metadata discovery and validation
- Token rotation and revocation
- SQLite-based storage adapter for Durable Objects

**Security:**
- Cryptographically secure token generation
- PKCE challenge verification (SHA-256)
- DPoP proof validation with replay protection
- Token binding to prevent token theft

**Compatibility:**
- Integrates with `@atproto/oauth-client` for client applications
- Storage interface allows custom backends beyond SQLite
- Built for Cloudflare Workers with Durable Objects

This package enables AT Protocol PDSs to act as OAuth providers, allowing users to authenticate with third-party applications using their PDS identity.
28 changes: 28 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: E2E Tests
on:
pull_request:
push:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- uses: pnpm/action-setup@v2
with:
version: 10.26.2
- name: Setup Node
uses: actions/setup-node@v6
with:
cache: "pnpm"
check-latest: true
registry-url: "https://registry.npmjs.org"
- name: Install dependencies
run: |
corepack enable
pnpm install
- name: Build
run: pnpm build
- name: E2E Tests
run: pnpm --filter @ascorbic/pds test:e2e
4 changes: 2 additions & 2 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"ignoreDependencies": ["@changesets/cli"]
},
"packages/pds": {
"ignore": ["scripts/**", "test/fixtures/**"],
"ignoreDependencies": ["tsx", "ws", "@types/ws"]
"ignore": ["scripts/**", "test/fixtures/**", "e2e/fixture/**"],
"ignoreDependencies": ["tsx", "vite", "@cloudflare/vite-plugin"]
},
"packages/create-pds": {
"ignore": ["templates/**"]
Expand Down
27 changes: 14 additions & 13 deletions packages/create-pds/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ const main = defineCommand({
args: {
name: {
type: "positional",
description: "Project name",
description: "Folder name",
required: false,
},
"package-manager": {
Expand Down Expand Up @@ -138,32 +138,33 @@ const main = defineCommand({
},
async run({ args }) {
const nonInteractive = args.yes || !process.stdout.isTTY;
p.intro("Create PDS");
p.intro("🦋 Create PDS");

p.log.info("Let's build your new home in the Atmosphere!");
p.log.warn(
"This is experimental software. Do not migrate your main Bluesky account yet.",
"This is experimental software. Don't migrate your main account yet.",
);

if (!nonInteractive) {
p.note("Use --yes to run non-interactively", "Tip");
p.log.message("Tip: Use --yes to skip prompts");
}

// Get project name
// Get folder name
let projectName = args.name;
if (!projectName) {
if (nonInteractive) {
projectName = "pds-worker";
projectName = "my-pds";
} else {
const result = await p.text({
message: "Project name:",
placeholder: "pds-worker",
defaultValue: "pds-worker",
message: "Folder name:",
placeholder: "my-pds",
defaultValue: "my-pds",
});
if (p.isCancel(result)) {
p.cancel("Cancelled");
process.exit(0);
}
projectName = result || "pds-worker";
projectName = result || "my-pds";
}
}

Expand Down Expand Up @@ -273,7 +274,7 @@ const main = defineCommand({

// Run pds init
if (!args["skip-init"] && !args["skip-install"]) {
p.log.info("Now let's configure your PDS for local development");
p.log.info("Now let's set up your account...");

try {
const pdsArgs = ["run", "pds", "init"];
Expand Down Expand Up @@ -305,10 +306,10 @@ const main = defineCommand({
"",
"Your PDS will be running at http://localhost:5173",
].join("\n"),
"Next steps",
"Next Steps",
);

p.outro("Happy building!");
p.outro("Welcome to the Atmosphere! 🦋");
},
});

Expand Down
Loading