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>
526 lines
17 KiB
TypeScript
526 lines
17 KiB
TypeScript
import { AtUri, ensureValidAtUri, ensureValidAtUriRegex } from '../src/index'
|
|
import * as readline from 'readline'
|
|
import * as fs from 'fs'
|
|
|
|
describe('At Uris', () => {
|
|
it('parses valid at uris', () => {
|
|
// input host path query hash
|
|
type AtUriTest = [string, string, string, string, string]
|
|
const TESTS: AtUriTest[] = [
|
|
['foo.com', 'foo.com', '', '', ''],
|
|
['at://foo.com', 'foo.com', '', '', ''],
|
|
['at://foo.com/', 'foo.com', '/', '', ''],
|
|
['at://foo.com/foo', 'foo.com', '/foo', '', ''],
|
|
['at://foo.com/foo/', 'foo.com', '/foo/', '', ''],
|
|
['at://foo.com/foo/bar', 'foo.com', '/foo/bar', '', ''],
|
|
['at://foo.com?foo=bar', 'foo.com', '', 'foo=bar', ''],
|
|
['at://foo.com?foo=bar&baz=buux', 'foo.com', '', 'foo=bar&baz=buux', ''],
|
|
['at://foo.com/?foo=bar', 'foo.com', '/', 'foo=bar', ''],
|
|
['at://foo.com/foo?foo=bar', 'foo.com', '/foo', 'foo=bar', ''],
|
|
['at://foo.com/foo/?foo=bar', 'foo.com', '/foo/', 'foo=bar', ''],
|
|
['at://foo.com#hash', 'foo.com', '', '', '#hash'],
|
|
['at://foo.com/#hash', 'foo.com', '/', '', '#hash'],
|
|
['at://foo.com/foo#hash', 'foo.com', '/foo', '', '#hash'],
|
|
['at://foo.com/foo/#hash', 'foo.com', '/foo/', '', '#hash'],
|
|
['at://foo.com?foo=bar#hash', 'foo.com', '', 'foo=bar', '#hash'],
|
|
|
|
[
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'',
|
|
'',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'',
|
|
'',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'/',
|
|
'',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'/foo',
|
|
'',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo/',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'/foo/',
|
|
'',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo/bar',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'/foo/bar',
|
|
'',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw?foo=bar',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'',
|
|
'foo=bar',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw?foo=bar&baz=buux',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'',
|
|
'foo=bar&baz=buux',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/?foo=bar',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'/',
|
|
'foo=bar',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo?foo=bar',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'/foo',
|
|
'foo=bar',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo/?foo=bar',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'/foo/',
|
|
'foo=bar',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw#hash',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'',
|
|
'',
|
|
'#hash',
|
|
],
|
|
[
|
|
'at://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/#hash',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'/',
|
|
'',
|
|
'#hash',
|
|
],
|
|
[
|
|
'at://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo#hash',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'/foo',
|
|
'',
|
|
'#hash',
|
|
],
|
|
[
|
|
'at://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw/foo/#hash',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'/foo/',
|
|
'',
|
|
'#hash',
|
|
],
|
|
[
|
|
'at://did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw?foo=bar#hash',
|
|
'did:example:EiAnKD8-jfdd0MDcZUjAbRgaThBrMxPTFOxcnfJhI7Ukaw',
|
|
'',
|
|
'foo=bar',
|
|
'#hash',
|
|
],
|
|
|
|
['did:web:localhost%3A1234', 'did:web:localhost%3A1234', '', '', ''],
|
|
['at://did:web:localhost%3A1234', 'did:web:localhost%3A1234', '', '', ''],
|
|
[
|
|
'at://did:web:localhost%3A1234/',
|
|
'did:web:localhost%3A1234',
|
|
'/',
|
|
'',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:web:localhost%3A1234/foo',
|
|
'did:web:localhost%3A1234',
|
|
'/foo',
|
|
'',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:web:localhost%3A1234/foo/',
|
|
'did:web:localhost%3A1234',
|
|
'/foo/',
|
|
'',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:web:localhost%3A1234/foo/bar',
|
|
'did:web:localhost%3A1234',
|
|
'/foo/bar',
|
|
'',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:web:localhost%3A1234?foo=bar',
|
|
'did:web:localhost%3A1234',
|
|
'',
|
|
'foo=bar',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:web:localhost%3A1234?foo=bar&baz=buux',
|
|
'did:web:localhost%3A1234',
|
|
'',
|
|
'foo=bar&baz=buux',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:web:localhost%3A1234/?foo=bar',
|
|
'did:web:localhost%3A1234',
|
|
'/',
|
|
'foo=bar',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:web:localhost%3A1234/foo?foo=bar',
|
|
'did:web:localhost%3A1234',
|
|
'/foo',
|
|
'foo=bar',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:web:localhost%3A1234/foo/?foo=bar',
|
|
'did:web:localhost%3A1234',
|
|
'/foo/',
|
|
'foo=bar',
|
|
'',
|
|
],
|
|
[
|
|
'at://did:web:localhost%3A1234#hash',
|
|
'did:web:localhost%3A1234',
|
|
'',
|
|
'',
|
|
'#hash',
|
|
],
|
|
[
|
|
'at://did:web:localhost%3A1234/#hash',
|
|
'did:web:localhost%3A1234',
|
|
'/',
|
|
'',
|
|
'#hash',
|
|
],
|
|
[
|
|
'at://did:web:localhost%3A1234/foo#hash',
|
|
'did:web:localhost%3A1234',
|
|
'/foo',
|
|
'',
|
|
'#hash',
|
|
],
|
|
[
|
|
'at://did:web:localhost%3A1234/foo/#hash',
|
|
'did:web:localhost%3A1234',
|
|
'/foo/',
|
|
'',
|
|
'#hash',
|
|
],
|
|
[
|
|
'at://did:web:localhost%3A1234?foo=bar#hash',
|
|
'did:web:localhost%3A1234',
|
|
'',
|
|
'foo=bar',
|
|
'#hash',
|
|
],
|
|
[
|
|
'at://4513echo.bsky.social/app.bsky.feed.post/3jsrpdyf6ss23',
|
|
'4513echo.bsky.social',
|
|
'/app.bsky.feed.post/3jsrpdyf6ss23',
|
|
'',
|
|
'',
|
|
],
|
|
]
|
|
for (const [uri, hostname, pathname, search, hash] of TESTS) {
|
|
const urip = new AtUri(uri)
|
|
expect(urip.protocol).toBe('at:')
|
|
expect(urip.host).toBe(hostname)
|
|
expect(urip.hostname).toBe(hostname)
|
|
expect(urip.origin).toBe(`at://${hostname}`)
|
|
expect(urip.pathname).toBe(pathname)
|
|
expect(urip.search).toBe(search)
|
|
expect(urip.hash).toBe(hash)
|
|
}
|
|
})
|
|
|
|
it('handles ATP-specific parsing', () => {
|
|
{
|
|
const urip = new AtUri('at://foo.com')
|
|
expect(urip.collection).toBe('')
|
|
expect(urip.rkey).toBe('')
|
|
}
|
|
{
|
|
const urip = new AtUri('at://foo.com/com.example.foo')
|
|
expect(urip.collection).toBe('com.example.foo')
|
|
expect(urip.rkey).toBe('')
|
|
}
|
|
{
|
|
const urip = new AtUri('at://foo.com/com.example.foo/123')
|
|
expect(urip.collection).toBe('com.example.foo')
|
|
expect(urip.rkey).toBe('123')
|
|
}
|
|
})
|
|
|
|
it('supports modifications', () => {
|
|
const urip = new AtUri('at://foo.com')
|
|
expect(urip.toString()).toBe('at://foo.com/')
|
|
|
|
urip.host = 'bar.com'
|
|
expect(urip.toString()).toBe('at://bar.com/')
|
|
urip.host = 'did:web:localhost%3A1234'
|
|
expect(urip.toString()).toBe('at://did:web:localhost%3A1234/')
|
|
urip.host = 'foo.com'
|
|
|
|
urip.pathname = '/'
|
|
expect(urip.toString()).toBe('at://foo.com/')
|
|
urip.pathname = '/foo'
|
|
expect(urip.toString()).toBe('at://foo.com/foo')
|
|
urip.pathname = 'foo'
|
|
expect(urip.toString()).toBe('at://foo.com/foo')
|
|
|
|
urip.collection = 'com.example.foo'
|
|
urip.rkey = '123'
|
|
expect(urip.toString()).toBe('at://foo.com/com.example.foo/123')
|
|
urip.rkey = '124'
|
|
expect(urip.toString()).toBe('at://foo.com/com.example.foo/124')
|
|
urip.collection = 'com.other.foo'
|
|
expect(urip.toString()).toBe('at://foo.com/com.other.foo/124')
|
|
urip.pathname = ''
|
|
urip.rkey = '123'
|
|
expect(urip.toString()).toBe('at://foo.com/undefined/123')
|
|
urip.pathname = 'foo'
|
|
|
|
urip.search = '?foo=bar'
|
|
expect(urip.toString()).toBe('at://foo.com/foo?foo=bar')
|
|
urip.searchParams.set('baz', 'buux')
|
|
expect(urip.toString()).toBe('at://foo.com/foo?foo=bar&baz=buux')
|
|
|
|
urip.hash = '#hash'
|
|
expect(urip.toString()).toBe('at://foo.com/foo?foo=bar&baz=buux#hash')
|
|
urip.hash = 'hash'
|
|
expect(urip.toString()).toBe('at://foo.com/foo?foo=bar&baz=buux#hash')
|
|
})
|
|
|
|
it('supports relative URIs', () => {
|
|
// input path query hash
|
|
type AtUriTest = [string, string, string, string]
|
|
const TESTS: AtUriTest[] = [
|
|
// input hostname pathname query hash
|
|
['', '', '', ''],
|
|
['/', '/', '', ''],
|
|
['/foo', '/foo', '', ''],
|
|
['/foo/', '/foo/', '', ''],
|
|
['/foo/bar', '/foo/bar', '', ''],
|
|
['?foo=bar', '', 'foo=bar', ''],
|
|
['?foo=bar&baz=buux', '', 'foo=bar&baz=buux', ''],
|
|
['/?foo=bar', '/', 'foo=bar', ''],
|
|
['/foo?foo=bar', '/foo', 'foo=bar', ''],
|
|
['/foo/?foo=bar', '/foo/', 'foo=bar', ''],
|
|
['#hash', '', '', '#hash'],
|
|
['/#hash', '/', '', '#hash'],
|
|
['/foo#hash', '/foo', '', '#hash'],
|
|
['/foo/#hash', '/foo/', '', '#hash'],
|
|
['?foo=bar#hash', '', 'foo=bar', '#hash'],
|
|
]
|
|
const BASES: string[] = [
|
|
'did:web:localhost%3A1234',
|
|
'at://did:web:localhost%3A1234',
|
|
'at://did:web:localhost%3A1234/foo/bar?foo=bar&baz=buux#hash',
|
|
'did:web:localhost%3A1234',
|
|
'at://did:web:localhost%3A1234',
|
|
'at://did:web:localhost%3A1234/foo/bar?foo=bar&baz=buux#hash',
|
|
]
|
|
|
|
for (const base of BASES) {
|
|
const basep = new AtUri(base)
|
|
for (const [relative, pathname, search, hash] of TESTS) {
|
|
const urip = new AtUri(relative, base)
|
|
expect(urip.protocol).toBe('at:')
|
|
expect(urip.host).toBe(basep.host)
|
|
expect(urip.hostname).toBe(basep.hostname)
|
|
expect(urip.origin).toBe(basep.origin)
|
|
expect(urip.pathname).toBe(pathname)
|
|
expect(urip.search).toBe(search)
|
|
expect(urip.hash).toBe(hash)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('AtUri validation', () => {
|
|
const expectValid = (h: string) => {
|
|
ensureValidAtUri(h)
|
|
ensureValidAtUriRegex(h)
|
|
}
|
|
const expectInvalid = (h: string) => {
|
|
expect(() => ensureValidAtUri(h)).toThrow()
|
|
expect(() => ensureValidAtUriRegex(h)).toThrow()
|
|
}
|
|
|
|
it('enforces spec basics', () => {
|
|
expectValid('at://did:plc:asdf123')
|
|
expectValid('at://user.bsky.social')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/record')
|
|
|
|
expectValid('at://did:plc:asdf123#/frag')
|
|
expectValid('at://user.bsky.social#/frag')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post#/frag')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/record#/frag')
|
|
|
|
expectInvalid('a://did:plc:asdf123')
|
|
expectInvalid('at//did:plc:asdf123')
|
|
expectInvalid('at:/a/did:plc:asdf123')
|
|
expectInvalid('at:/did:plc:asdf123')
|
|
expectInvalid('AT://did:plc:asdf123')
|
|
expectInvalid('http://did:plc:asdf123')
|
|
expectInvalid('://did:plc:asdf123')
|
|
expectInvalid('at:did:plc:asdf123')
|
|
expectInvalid('at:/did:plc:asdf123')
|
|
expectInvalid('at:///did:plc:asdf123')
|
|
expectInvalid('at://:/did:plc:asdf123')
|
|
expectInvalid('at:/ /did:plc:asdf123')
|
|
expectInvalid('at://did:plc:asdf123 ')
|
|
expectInvalid('at://did:plc:asdf123/ ')
|
|
expectInvalid(' at://did:plc:asdf123')
|
|
expectInvalid('at://did:plc:asdf123/com.atproto.feed.post ')
|
|
expectInvalid('at://did:plc:asdf123/com.atproto.feed.post# ')
|
|
expectInvalid('at://did:plc:asdf123/com.atproto.feed.post#/ ')
|
|
expectInvalid('at://did:plc:asdf123/com.atproto.feed.post#/frag ')
|
|
expectInvalid('at://did:plc:asdf123/com.atproto.feed.post#fr ag')
|
|
expectInvalid('//did:plc:asdf123')
|
|
expectInvalid('at://name')
|
|
expectInvalid('at://name.0')
|
|
expectInvalid('at://diD:plc:asdf123')
|
|
expectInvalid('at://did:plc:asdf123/com.atproto.feed.p@st')
|
|
expectInvalid('at://did:plc:asdf123/com.atproto.feed.p$st')
|
|
expectInvalid('at://did:plc:asdf123/com.atproto.feed.p%st')
|
|
expectInvalid('at://did:plc:asdf123/com.atproto.feed.p&st')
|
|
expectInvalid('at://did:plc:asdf123/com.atproto.feed.p()t')
|
|
expectInvalid('at://did:plc:asdf123/com.atproto.feed_post')
|
|
expectInvalid('at://did:plc:asdf123/-com.atproto.feed.post')
|
|
expectInvalid('at://did:plc:asdf@123/com.atproto.feed.post')
|
|
|
|
expectInvalid('at://DID:plc:asdf123')
|
|
expectInvalid('at://user.bsky.123')
|
|
expectInvalid('at://bsky')
|
|
expectInvalid('at://did:plc:')
|
|
expectInvalid('at://did:plc:')
|
|
expectInvalid('at://frag')
|
|
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/' + 'o'.repeat(800))
|
|
expectInvalid(
|
|
'at://did:plc:asdf123/com.atproto.feed.post/' + 'o'.repeat(8200),
|
|
)
|
|
})
|
|
|
|
it('has specified behavior on edge cases', () => {
|
|
expectInvalid('at://user.bsky.social//')
|
|
expectInvalid('at://user.bsky.social//com.atproto.feed.post')
|
|
expectInvalid('at://user.bsky.social/com.atproto.feed.post//')
|
|
expectInvalid(
|
|
'at://did:plc:asdf123/com.atproto.feed.post/asdf123/more/more',
|
|
)
|
|
expectInvalid('at://did:plc:asdf123/short/stuff')
|
|
expectInvalid('at://did:plc:asdf123/12345')
|
|
})
|
|
|
|
it('enforces no trailing slashes', () => {
|
|
expectValid('at://did:plc:asdf123')
|
|
expectInvalid('at://did:plc:asdf123/')
|
|
|
|
expectValid('at://user.bsky.social')
|
|
expectInvalid('at://user.bsky.social/')
|
|
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post')
|
|
expectInvalid('at://did:plc:asdf123/com.atproto.feed.post/')
|
|
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/record')
|
|
expectInvalid('at://did:plc:asdf123/com.atproto.feed.post/record/')
|
|
expectInvalid('at://did:plc:asdf123/com.atproto.feed.post/record/#/frag')
|
|
})
|
|
|
|
it('enforces strict paths', () => {
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/asdf123')
|
|
expectInvalid('at://did:plc:asdf123/com.atproto.feed.post/asdf123/asdf')
|
|
})
|
|
|
|
it('is very permissive about record keys', () => {
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/asdf123')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/a')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/%23')
|
|
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/$@!*)(:,;~.sdf123')
|
|
expectValid("at://did:plc:asdf123/com.atproto.feed.post/~'sdf123")
|
|
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/$')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/@')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/!')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/*')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/(')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/,')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/;')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/abc%30123')
|
|
})
|
|
|
|
it('is probably too permissive about URL encoding', () => {
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/%30')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/%3')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/%')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/%zz')
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post/%%%')
|
|
})
|
|
|
|
it('is very permissive about fragments', () => {
|
|
expectValid('at://did:plc:asdf123#/frac')
|
|
|
|
expectInvalid('at://did:plc:asdf123#')
|
|
expectInvalid('at://did:plc:asdf123##')
|
|
expectInvalid('#at://did:plc:asdf123')
|
|
expectInvalid('at://did:plc:asdf123#/asdf#/asdf')
|
|
|
|
expectValid('at://did:plc:asdf123#/com.atproto.feed.post')
|
|
expectValid('at://did:plc:asdf123#/com.atproto.feed.post/')
|
|
expectValid('at://did:plc:asdf123#/asdf/')
|
|
|
|
expectValid('at://did:plc:asdf123/com.atproto.feed.post#/$@!*():,;~.sdf123')
|
|
expectValid('at://did:plc:asdf123#/[asfd]')
|
|
|
|
expectValid('at://did:plc:asdf123#/$')
|
|
expectValid('at://did:plc:asdf123#/*')
|
|
expectValid('at://did:plc:asdf123#/;')
|
|
expectValid('at://did:plc:asdf123#/,')
|
|
})
|
|
|
|
it('conforms to interop valid ATURIs', () => {
|
|
const lineReader = readline.createInterface({
|
|
input: fs.createReadStream(
|
|
`${__dirname}/interop-files/aturi_syntax_valid.txt`,
|
|
),
|
|
terminal: false,
|
|
})
|
|
lineReader.on('line', (line) => {
|
|
if (line.startsWith('#') || line.length === 0) {
|
|
return
|
|
}
|
|
expectValid(line)
|
|
})
|
|
})
|
|
|
|
// NOTE: this package is currently more permissive than spec about AT URIs, so invalid cases are not errors
|
|
})
|