Skip to content

Conversation

@aspiers
Copy link
Collaborator

@aspiers aspiers commented Dec 9, 2025

This PR includes several improvements to the SDS organization creation flow and the SDS demo application.

Main Fix: Require handlePrefix for org creation

Problem

Previously, the SDS server was auto-generating organization handles by appending a timestamp to the organization name (e.g., name-1234567890). This approach had several critical issues:

  1. Invalid handles: Generated handles violated ATProto handle specification requirements
  2. No uniqueness guarantee: Timestamp-based generation doesn't guarantee uniqueness across concurrent requests
  3. Broken suffix requirement: Handles must have a suffix matching the PDS/SDS hostname, but auto-generated handles didn't include this
  4. Poor user experience: Users had no control over their organization handles

Solution

This PR changes the API to require a handlePrefix parameter from the client, and the SDS server appends its hostname as the suffix to form a valid FQDN handle. This ensures:

  • ✅ Handles are valid according to ATProto specification
  • ✅ Handles have the correct suffix matching the SDS hostname
  • ✅ No timestamp-based collision risk
  • ✅ User has control over the handle prefix
  • ✅ Proper validation on both client and server

Changes

API Changes

  • Lexicon: Changed com.sds.organization.create to require handlePrefix instead of optional handle
  • Server: Validates handle format using @atproto/syntax and checks for uniqueness before creating
  • Server: Uses hostname (not host) to exclude port numbers from handles

Frontend Changes

  • UI: Added real-time handle validation with clear error messages
  • UI: Added debounced handle availability checking (1 second delay)
  • UI: Visual feedback (green/red borders) for handle validation status
  • UI: "Create Repository" button disabled until handle is valid and available
  • UI: Shows preview of full handle (prefix + hostname) as user types

Validation

  • Client-side: Uses @atproto/syntax to validate handle format in real-time
  • Server-side: Validates full handle format and checks for duplicates
  • Availability: Checks handle availability via com.atproto.identity.resolveHandle

Additional Fixes

OAuth Scope Improvements

  • Conditional moderation scope: Only includes include:com.atproto.moderation.basePermissions when using local dev PDS (where the lexicon is registered), preventing invalid_scope errors with external PDS instances
  • Reduced scope permissions: Removed unnecessary scopes (identity:*, account:status, blob:*/*, rpc:*?aud=did:web:bsky.app#bsky_appview) to follow principle of least privilege and avoid scary "could steal your account" warnings
  • Unified dev/prod scopes: Simplified scope configuration by using the same minimal set for both environments

Other Improvements

  • Dotenv support: Added support for loading environment variables from .env files
  • Graceful error handling: Improved handling of accounts without profiles
  • Code cleanup: Removed unnecessary console logging

Testing

  • ✅ Updated integration tests to use handlePrefix parameter
  • ✅ Tests verify handle format validation
  • ✅ Tests verify duplicate handle rejection
  • ✅ Tests verify successful organization creation with valid prefix

Migration Notes

This is a breaking change for the com.sds.organization.create API:

  • The handle parameter has been removed
  • The handlePrefix parameter is now required
  • Clients must update to send handlePrefix instead of handle

Commits Included

  1. fix(sds): require handlePrefix for org creation instead of auto-generating
  2. feat(sds-demo): support dotenv loading of variables
  3. fix(sds-demo): gracefully handle accounts without profiles
  4. fix(sds-demo): reduce OAuth scopes to minimum required permissions
  5. fix(sds-demo): conditionally include moderation scope for local dev PDS
  6. chore(oauth-provider-ui): remove unnecessary console logging

aspiers and others added 6 commits December 9, 2025 19:52
Without this patch, the OAuth scope always includes
include:com.atproto.moderation.basePermissions in development mode,
regardless of which PDS instance is being used.

This is a problem because
com.atproto.moderation.basePermissions is a permission-set lexicon
that is only registered in the local dev environment (via the
dev-env LexiconAuthorityProfile). External PDS instances like
pds-eu-west4.test.certified.app don't have this lexicon registered,
causing OAuth authorization requests to fail with "invalid_scope"
errors: "Could not resolve Lexicon for NSID
(com.atproto.moderation.basePermissions)".

This patch solves the problem by checking if the SIGN_UP_URL points
to a localhost PDS instance before including the moderation
permissions scope. The scope is now only included when using the
local dev PDS (localhost:2583) where the lexicon is actually
available, allowing the demo to work with both local and external
PDS instances.

Co-authored-by: Composer <noreply@cursor.com>
Without this patch, the SDS demo requests overly broad OAuth scopes
including identity:* (which allows changing handle and could steal
accounts), account:status, blob:*/*, and rpc:*?aud=did:web:bsky.app#bsky_appview
in production, even though the demo doesn't use these permissions.

This is a problem because:
- identity:* triggers scary "could steal your account" warnings in OAuth
  consent screens, reducing user trust
- The demo requests permissions it doesn't actually need, violating
  principle of least privilege
- Production and development scopes differed unnecessarily, making
  maintenance harder

This patch solves the problem by:
- Removing identity:* scope (DID is already in token, no identity scope
  needed)
- Removing unused production scopes (account:status, blob, bsky.app RPC)
- Unifying dev and prod scopes to the same minimal set since they're
  identical
- Adding inline comments explaining why each remaining scope is needed:
  - atproto: basic scope required for all AT Protocol operations
  - account:email: read email address for UI display
  - repo:*: create and read records in shared repositories

The moderation permissions lexicon scope remains conditionally included
only for local dev PDS instances where it's available.

Co-authored-by: Composer <noreply@cursor.com>
…ating

Previously, the SDS server was auto-generating organization handles by
appending a timestamp (e.g., 'name-1234567890'), which created invalid
handles that violated ATProto handle specification requirements.

Now the API requires a unique handlePrefix to be specified for org
creation, and the SDS server appends its hostname as the suffix to
form a valid FQDN handle. This ensures:
- Handles are valid according to ATProto specification
- Handles have the correct suffix matching the SDS hostname
- No timestamp-based collision risk
- User has control over the handle prefix

Includes both client-side and server-side validation using
@atproto/syntax to ensure handle format compliance.
@vercel
Copy link

vercel bot commented Dec 9, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
sds-demo Ready Ready Preview Comment Dec 9, 2025 7:58pm

@aspiers
Copy link
Collaborator Author

aspiers commented Dec 10, 2025

Merging as discussed in team call just now.

@aspiers aspiers merged commit 9074a02 into dev Dec 10, 2025
4 of 5 checks passed
@aspiers aspiers deleted the valid-handles branch December 10, 2025 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants