@atproto/lex-server 0.0.17 → 0.1.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,87 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import { serviceAuth } from './service-auth.js'
3
+
4
+ describe('serviceAuth - lxm validation', () => {
5
+ const audience = 'did:web:api.example.com'
6
+ const issuer = 'did:web:caller.example.com'
7
+ const nsid = 'io.example.test'
8
+
9
+ function makeJwt(payload: Record<string, unknown>): string {
10
+ const header = Buffer.from(
11
+ JSON.stringify({ alg: 'ES256K', typ: 'JWT' }),
12
+ ).toString('base64url')
13
+ const body = Buffer.from(JSON.stringify(payload)).toString('base64url')
14
+ const sig = Buffer.from([0]).toString('base64url')
15
+ return `${header}.${body}.${sig}`
16
+ }
17
+
18
+ function basePayload(overrides: Record<string, unknown> = {}) {
19
+ const now = Math.floor(Date.now() / 1000)
20
+ return { iss: issuer, aud: audience, iat: now, exp: now + 60, ...overrides }
21
+ }
22
+
23
+ function setup() {
24
+ const resolve = vi.fn(async () => {
25
+ throw new Error('stop after lxm check')
26
+ })
27
+ const auth = serviceAuth({
28
+ audience,
29
+ unique: async () => true,
30
+ didResolver: { resolve },
31
+ })
32
+ return { auth, resolve }
33
+ }
34
+
35
+ it('rejects with BadJwtLexiconMethod when lxm does not match method.nsid', async () => {
36
+ const { auth, resolve } = setup()
37
+ const jwt = makeJwt(basePayload({ lxm: 'io.example.different' }))
38
+ const request = new Request(`https://api.example.com/xrpc/${nsid}`, {
39
+ headers: { authorization: `Bearer ${jwt}` },
40
+ })
41
+
42
+ await expect(
43
+ auth({ request, method: { nsid } as any, params: {} }),
44
+ ).rejects.toThrow('Invalid JWT lexicon method ("lxm")')
45
+ expect(resolve).not.toHaveBeenCalled()
46
+ })
47
+
48
+ it('passes lxm check when payload.lxm matches method.nsid', async () => {
49
+ const { auth, resolve } = setup()
50
+ const jwt = makeJwt(basePayload({ lxm: nsid }))
51
+ const request = new Request(`https://api.example.com/xrpc/${nsid}`, {
52
+ headers: { authorization: `Bearer ${jwt}` },
53
+ })
54
+
55
+ await expect(
56
+ auth({ request, method: { nsid } as any, params: {} }),
57
+ ).rejects.toThrow()
58
+ // The DID resolver isn't called unless "lxm" validation succeeded
59
+ expect(resolve).toHaveBeenCalled()
60
+ })
61
+
62
+ it('skips lxm check when payload has no lxm claim', async () => {
63
+ const { auth, resolve } = setup()
64
+ const jwt = makeJwt(basePayload())
65
+ const request = new Request(`https://api.example.com/xrpc/${nsid}`, {
66
+ headers: { authorization: `Bearer ${jwt}` },
67
+ })
68
+
69
+ await expect(
70
+ auth({ request, method: { nsid } as any, params: {} }),
71
+ ).rejects.toThrow()
72
+ expect(resolve).toHaveBeenCalled()
73
+ })
74
+
75
+ it('rejects an empty-string lxm claim against a real NSID', async () => {
76
+ const { auth, resolve } = setup()
77
+ const jwt = makeJwt(basePayload({ lxm: '' }))
78
+ const request = new Request(`https://api.example.com/xrpc/${nsid}`, {
79
+ headers: { authorization: `Bearer ${jwt}` },
80
+ })
81
+
82
+ await expect(
83
+ auth({ request, method: { nsid } as any, params: {} }),
84
+ ).rejects.toThrow('Invalid JWT lexicon method ("lxm")')
85
+ expect(resolve).not.toHaveBeenCalled()
86
+ })
87
+ })
@@ -442,7 +442,7 @@ async function parseJwt(
442
442
  )
443
443
  }
444
444
 
445
- if (payload.lxm != null && typeof payload.lxm !== options.lxm) {
445
+ if (payload.lxm != null && payload.lxm !== options.lxm) {
446
446
  throw new LexServerAuthError(
447
447
  'AuthenticationRequired',
448
448
  'Invalid JWT lexicon method ("lxm")',
File without changes