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>
159 lines
5.0 KiB
TypeScript
159 lines
5.0 KiB
TypeScript
import {
|
|
ensureValidNsid,
|
|
ensureValidNsidRegex,
|
|
InvalidNsidError,
|
|
NSID,
|
|
} from '../src'
|
|
import * as readline from 'readline'
|
|
import * as fs from 'fs'
|
|
|
|
describe('NSID parsing & creation', () => {
|
|
it('parses valid NSIDs', () => {
|
|
expect(NSID.parse('com.example.foo').authority).toBe('example.com')
|
|
expect(NSID.parse('com.example.foo').name).toBe('foo')
|
|
expect(NSID.parse('com.example.foo').toString()).toBe('com.example.foo')
|
|
expect(NSID.parse('com.long-thing1.cool.fooBarBaz').authority).toBe(
|
|
'cool.long-thing1.com',
|
|
)
|
|
expect(NSID.parse('com.long-thing1.cool.fooBarBaz').name).toBe('fooBarBaz')
|
|
expect(NSID.parse('com.long-thing1.cool.fooBarBaz').toString()).toBe(
|
|
'com.long-thing1.cool.fooBarBaz',
|
|
)
|
|
})
|
|
|
|
it('creates valid NSIDs', () => {
|
|
expect(NSID.create('example.com', 'foo').authority).toBe('example.com')
|
|
expect(NSID.create('example.com', 'foo').name).toBe('foo')
|
|
expect(NSID.create('example.com', 'foo').toString()).toBe('com.example.foo')
|
|
expect(NSID.create('cool.long-thing1.com', 'fooBarBaz').authority).toBe(
|
|
'cool.long-thing1.com',
|
|
)
|
|
expect(NSID.create('cool.long-thing1.com', 'fooBarBaz').name).toBe(
|
|
'fooBarBaz',
|
|
)
|
|
expect(NSID.create('cool.long-thing1.com', 'fooBarBaz').toString()).toBe(
|
|
'com.long-thing1.cool.fooBarBaz',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('NSID validation', () => {
|
|
const expectValid = (h: string) => {
|
|
ensureValidNsid(h)
|
|
ensureValidNsidRegex(h)
|
|
}
|
|
const expectInvalid = (h: string) => {
|
|
expect(() => ensureValidNsid(h)).toThrow(InvalidNsidError)
|
|
expect(() => ensureValidNsidRegex(h)).toThrow(InvalidNsidError)
|
|
}
|
|
|
|
it('enforces spec details', () => {
|
|
expectValid('com.example.foo')
|
|
const longNsid = 'com.' + 'o'.repeat(63) + '.foo'
|
|
expectValid(longNsid)
|
|
|
|
const tooLongNsid = 'com.' + 'o'.repeat(64) + '.foo'
|
|
expectInvalid(tooLongNsid)
|
|
|
|
const longEnd = 'com.example.' + 'o'.repeat(63)
|
|
expectValid(longEnd)
|
|
|
|
const tooLongEnd = 'com.example.' + 'o'.repeat(64)
|
|
expectInvalid(tooLongEnd)
|
|
|
|
const longOverall = 'com.' + 'middle.'.repeat(40) + 'foo'
|
|
expect(longOverall.length).toBe(287)
|
|
expectValid(longOverall)
|
|
|
|
const tooLongOverall = 'com.' + 'middle.'.repeat(50) + 'foo'
|
|
expect(tooLongOverall.length).toBe(357)
|
|
expectInvalid(tooLongOverall)
|
|
|
|
expectValid('com.example.fooBar')
|
|
expectValid('net.users.bob.ping')
|
|
expectValid('a.b.c')
|
|
expectValid('m.xn--masekowski-d0b.pl')
|
|
expectValid('one.two.three')
|
|
expectValid('one.two.three.four-and.FiVe')
|
|
expectValid('one.2.three')
|
|
expectValid('a-0.b-1.c')
|
|
expectValid('a0.b1.cc')
|
|
expectValid('cn.8.lex.stuff')
|
|
expectValid('test.12345.record')
|
|
expectValid('a01.thing.record')
|
|
expectValid('a.0.c')
|
|
expectValid('xn--fiqs8s.xn--fiqa61au8b7zsevnm8ak20mc4a87e.record.two')
|
|
|
|
expectInvalid('com.example.foo.*')
|
|
expectInvalid('com.example.foo.blah*')
|
|
expectInvalid('com.example.foo.*blah')
|
|
expectInvalid('com.example.f00')
|
|
expectInvalid('com.exa💩ple.thing')
|
|
expectInvalid('a-0.b-1.c-3')
|
|
expectInvalid('a-0.b-1.c-o')
|
|
expectInvalid('a0.b1.c3')
|
|
expectInvalid('1.0.0.127.record')
|
|
expectInvalid('0two.example.foo')
|
|
expectInvalid('example.com')
|
|
expectInvalid('com.example')
|
|
expectInvalid('a.')
|
|
expectInvalid('.one.two.three')
|
|
expectInvalid('one.two.three ')
|
|
expectInvalid('one.two..three')
|
|
expectInvalid('one .two.three')
|
|
expectInvalid(' one.two.three')
|
|
expectInvalid('com.exa💩ple.thing')
|
|
expectInvalid('com.atproto.feed.p@st')
|
|
expectInvalid('com.atproto.feed.p_st')
|
|
expectInvalid('com.atproto.feed.p*st')
|
|
expectInvalid('com.atproto.feed.po#t')
|
|
expectInvalid('com.atproto.feed.p!ot')
|
|
expectInvalid('com.example-.foo')
|
|
})
|
|
|
|
it('allows onion (Tor) NSIDs', () => {
|
|
expectValid('onion.expyuzz4wqqyqhjn.spec.getThing')
|
|
expectValid(
|
|
'onion.g2zyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.lex.deleteThing',
|
|
)
|
|
})
|
|
|
|
it('allows starting-with-numeric segments (same as domains)', () => {
|
|
expectValid('org.4chan.lex.getThing')
|
|
expectValid('cn.8.lex.stuff')
|
|
expectValid(
|
|
'onion.2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.lex.deleteThing',
|
|
)
|
|
})
|
|
|
|
it('conforms to interop valid NSIDs', () => {
|
|
const lineReader = readline.createInterface({
|
|
input: fs.createReadStream(
|
|
`${__dirname}/interop-files/nsid_syntax_valid.txt`,
|
|
),
|
|
terminal: false,
|
|
})
|
|
lineReader.on('line', (line) => {
|
|
if (line.startsWith('#') || line.length === 0) {
|
|
return
|
|
}
|
|
expectValid(line)
|
|
})
|
|
})
|
|
|
|
it('conforms to interop invalid NSIDs', () => {
|
|
const lineReader = readline.createInterface({
|
|
input: fs.createReadStream(
|
|
`${__dirname}/interop-files/nsid_syntax_invalid.txt`,
|
|
),
|
|
terminal: false,
|
|
})
|
|
lineReader.on('line', (line) => {
|
|
if (line.startsWith('#') || line.length === 0) {
|
|
return
|
|
}
|
|
expectInvalid(line)
|
|
})
|
|
})
|
|
})
|