a8d6c11235
* chore(deps): update zod * chore(deps): update pino to match entryway version * chore(tsconfig): remove truncation of types through noErrorTruncation * add support for DPoP token type when logging * fix(bsky): JSON.parse does not return value of type JSON * fix(pds): add res property to ReqCtx * fix(pds): properly type getPreferences return value * chore(tsconfig): disable noFallthroughCasesInSwitch * refactor(pds): move tracer config in own file * feat(dev-env): start with "pnpm dev" * feat(oauth): add oauth provider & client libs * feat(pds): add oauth provider * chore: changeset * feat: various fixes and improvements * chore(deps): update better-sqlite3 to version 10.0.0 for node 22 compatibility * chore(deps): drop unused tslib * fix(did): normalize service IDs before looking for duplicates * fix(did): avoid minor type casting * fix(did): improve argument validation * fix(fetch): explicit use of negation around number comparison * fix(oauth-provider): improve argument validation * feat(did): add ATPROTO specific "isAtprotoDidWeb" method * feat(rollup-plugin-bundle-manifest): add readme * feat(lint): add eqeqeq rule (only allow == and != with null) * fix(oauth-client-browser): typo in gitignore * fix(oauth-provider): properly name error class file * fix(oauth-provider): remove un-necessary useMemo * fix(did-resolver): properly build did:web document url * fix(did-resolver): remove unused types * fix(fetch): remove unused utils * fix(pds): remove unused script and dependency * fix(oauth-provider): simplify isSubPath util * fix(oauth-provider): add InvalidRedirectUriError static constructor * fix(jwk): improve JWT validation to provide better error messages and distinguish between signed and unsigned tokens * fix(pds): use "debug" log level for fetch method * fix(pds): allow access tokens to contain an unknown "typ" claim (with the exception of "dpop+jwt") * fix(jwk): remove un-necessary code * fix(pds): account for whitespace chars when checking JSON * fix(pds): remove oauth specific config * fix(pds): run all write queries through transaction or executeWithRetry fix(pds): remove outdated comments fix(pds): rename used_refresh_token columns & added primary key fix(pds): run cleanup task through backgroundQueue fix(pds): add device.id foreign key to device_account fix(pds): add comment on cleanup of used_refresh_token fix(pds): add primary key on device_account * fix(oauth-provider:time): simplify constantTime util * fix(pds): rename disableSsrf into disableSsrfProtection * fix(oauth-client-react-native): remove incomplete package * refactor(pds): remove status & active from ActorAccount * fix(pds): invalidate all oauth tokens on takedown * fix(oauth-provider): enforce token expiry * fix(pds): properly support deactivated accounts * perf(pds:db): allow transaction function to be sync * refactor(psq:account-manager): expose only query builders & data transformations utils from helpers * fix(oauth-provider): imports from self * fix(ci): add nested packages to build artifacts * style(fetch): rename TODO into @TODO * style(rollup-plugin-bundle-manifest): remove "TODO" from comment * style(oauth-client): rename TODO into @TODO * style(oauth-provider): rename TODO into @TODO * refactor(oauth-client): remove "OAuth" prefix from types * fix(oauth-client-browser): better type SessionListener * style(oauth): rename TODO into @TODO * fix(oauth-provider): enforce provider max session age * fix(oauth-provider): check authentication parameters against all client metadata * fix(api): tests * fix(pds): remove .js from imports for tests * fix(pds): change account status to match tests * chore(deps): make all packages depend on the same zod version * fix(common-web): remove un-necessary binding of Checkable to "zod" * refactor(jwk): infer jwt schema from refinement definition * fix(handle-resolver): allow resolution errors to propagate docs(handle-resolver): better handling of DNS resolution errors fix(handle-resolver): properly handle DOH responses * fix(did): service endpoint arrays must contain "one or more" element * refactor(pipe): simplify implementation * fix(pds): add missing DB indexes * feat(oauth): Resolve Authorization Server URI through Protected Resource Metadata * style:(oauth-client): import order * docs(oauth-provider:redirect-uri): add reference url * feat(oauth): implement "OAuth Client ID Metadata Document" from draft-parecki-oauth-client-id-metadata-document-latest internet draft * feat(oauth-client): backport changes from feat-oauth-client * docs(simple-store): improve comments * feat(lexicons): add iterable capabilities * fix(pds): type error in dev mode * feat(oauth-provider): improved error reporting * fix(oauth-types): allow insecure issuer during tests * fix(xrpc-server): allow upload of empty files * fix: lint * feat(fetch): keep request reference in errors feat(fetch): utilities improvements * fix(pds): allow more than one session token per user * feat(ozone): improve env validation error messages * fix(oauth-client): account for DPoP when checking for invalid_token errors * fixup! feat(fetch): keep request reference in errors feat(fetch): utilities improvements * fixup! feat(fetch): keep request reference in errors feat(fetch): utilities improvements * fix(oauth): various validation fixes feat(oauth): share client_id validation and parsing utilities between client & provider * feat(dev-env): fix ozone port number * fix(fetch-node): prevent fetch against invalid domain names * fix(oauth-provider): add typings for psl dep * feat(jwk): make type def compatible with TS 4.x * fix(oauth): fixed various spec compliance fix(oauth): return "sub" in refresh token response fix(oauth): limit token validity for third party clients fix(oauth): hide client image when not trusted * fix(oauth): lint * pds: switch changeset to patch, no breaking changes * changeset and config for new oauth deps --------- Co-authored-by: Devin Ivy <devinivy@gmail.com>
149 lines
4.9 KiB
Markdown
149 lines
4.9 KiB
Markdown
# Universal Handle Resolver implementation for ATPROTO
|
|
|
|
This package provides a handle resolver implementation for ATPROTO. It is used
|
|
to resolve handles to their corresponding DID.
|
|
|
|
This package is meant to be used in any JavaScript environment that support the
|
|
`fetch()` function. Because APTORO handle resolution requires DNS resolution,
|
|
you will need to provide your own DNS resolution function when using this
|
|
package.
|
|
|
|
There are two main classes in this package:
|
|
|
|
- `AtprotoHandleResolver` This implements the official ATPROTO handle resolution
|
|
algorithm (and requires a DNS resolver).
|
|
- `AppViewHandleResolver` This uses HTTP requests to the Bluesky AppView
|
|
(bsky.app) to provide handle resolution.
|
|
|
|
## Usage
|
|
|
|
### From a front-end app
|
|
|
|
Since the ATPROTO handle resolution algorithm requires DNS resolution, and the
|
|
browser does not provide a built-in DNS resolver, this package offers two
|
|
options:
|
|
|
|
- Delegate handle resolution to an AppView (`AppViewHandleResolver`). This is
|
|
the recommended approach for front-end apps.
|
|
- Use a DNS-over-HTTPS (DoH) server (`DohHandleResolver`). Prefer this method
|
|
if you don't own an AppView and already have a DoH server that you trust.
|
|
|
|
Using an AppView:
|
|
|
|
> [!CAUTION]
|
|
> Use the Bluesky owned AppView (`https://api.bsky.app/`), or PDS
|
|
> (`https://bsky.social/`), at your own risk. Using these servers in a
|
|
> third-party application might expose your users' data (IP address) to Bluesky.
|
|
> Bluesky might log the data sent to it when your app is resolving handles.
|
|
> Bluesky might also change the API, or terms or use, at any time without
|
|
> notice. Make sure you are compliant with the Bluesky terms of use as well as
|
|
> any laws and regulations that apply to your use case.
|
|
|
|
```ts
|
|
import { AppViewHandleResolver } from '@atproto-labs/handle-resolver'
|
|
|
|
const resolver = new AppViewHandleResolver({
|
|
service: 'https://my-app-view.com/',
|
|
})
|
|
const did = await resolver.resolve('my-handle.bsky.social')
|
|
```
|
|
|
|
Using DNS-over-HTTPS (DoH) for DNS resolution:
|
|
|
|
> [!CAUTION]
|
|
> Using a DoH server that you don't own might expose your users' data to
|
|
> the DoH server provider. The DoH server provider might log the data sent to it
|
|
> by your app, allowing them to track which handles are being resolved by your
|
|
> users. In the browser, it is recommended to use a DoH server that you own and
|
|
> control. Or to implement your own AppView and use the `AppViewHandleResolver`
|
|
> class.
|
|
|
|
> [!NOTE]
|
|
> Using the `DohHandleResolver` requires a DNS-over-HTTPS server that
|
|
> supports the DNS-over-HTTPS protocol with "application/dns-json" responses.
|
|
|
|
```ts
|
|
import { DohHandleResolver } from '@atproto-labs/handle-resolver'
|
|
|
|
// Also works with 'https://cloudflare-dns.com/dns-query'
|
|
const resolver = new DohHandleResolver('https://dns.google/resolve', {
|
|
// Optional: Custom fetch function that will be used both for DNS resolution
|
|
// and well-known resolution.
|
|
fetch: globalThis.fetch.bind(globalThis),
|
|
})
|
|
|
|
const did = await resolver.resolve('my-handle.bsky.social')
|
|
```
|
|
|
|
### From a Node.js app
|
|
|
|
> [!NOTE]
|
|
> On a Node.js backend, you will probably want to use the
|
|
> "@atproto-labs/handle-resolver-node" package. The example below applies to
|
|
> Node.js code running on a user's machine (e.g. through Electron).
|
|
|
|
```ts
|
|
import { AtprotoHandleResolver } from '@atproto-labs/handle-resolver'
|
|
import { resolveTxt } from 'node:dns/promises'
|
|
|
|
const resolver = new AtprotoHandleResolver({
|
|
// Optional: Custom fetch function (used for well-known resolution)
|
|
fetch: globalThis.fetch.bind(globalThis),
|
|
|
|
resolveTxt: async (domain: string) =>
|
|
resolveTxt(domain).then((chunks) => chunks.join('')),
|
|
})
|
|
```
|
|
|
|
### Caching
|
|
|
|
Using a default, in-memory cache, in which items expire after 10 minutes:
|
|
|
|
```ts
|
|
import {
|
|
AppViewHandleResolver,
|
|
CachedHandleResolver,
|
|
HandleResolver,
|
|
HandleCache,
|
|
} from '@atproto-labs/handle-resolver'
|
|
|
|
// See previous examples for creating a resolver
|
|
declare const sourceResolver: HandleResolver
|
|
|
|
const resolver = new CachedHandleResolver(sourceResolver)
|
|
const did = await resolver.resolve('my-handle.bsky.social')
|
|
const did = await resolver.resolve('my-handle.bsky.social') // Result from cache
|
|
const did = await resolver.resolve('my-handle.bsky.social') // Result from cache
|
|
```
|
|
|
|
Using a custom cache:
|
|
|
|
```ts
|
|
import {
|
|
AppViewHandleResolver,
|
|
CachedHandleResolver,
|
|
HandleResolver,
|
|
HandleCache,
|
|
} from '@atproto-labs/handle-resolver'
|
|
|
|
// See previous examples for creating a resolver
|
|
declare const sourceResolver: HandleResolver
|
|
|
|
const cache: HandleCache = {
|
|
set(handle, did): Promise<void> {
|
|
/* TODO */
|
|
},
|
|
get(handle): Promise<undefined | string> {
|
|
/* TODO */
|
|
},
|
|
del(handle): Promise<void> {
|
|
/* TODO */
|
|
},
|
|
}
|
|
|
|
const resolver = new CachedHandleResolver(sourceResolver, cache)
|
|
const did = await resolver.resolve('my-handle.bsky.social')
|
|
const did = await resolver.resolve('my-handle.bsky.social') // Result from cache
|
|
const did = await resolver.resolve('my-handle.bsky.social') // Result from cache
|
|
```
|