@atproto/oauth-scopes 0.0.2 → 0.1.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.
- package/CHANGELOG.md +25 -0
- package/dist/atproto-oauth-scope.d.ts +12 -0
- package/dist/atproto-oauth-scope.d.ts.map +1 -0
- package/dist/atproto-oauth-scope.js +32 -0
- package/dist/atproto-oauth-scope.js.map +1 -0
- package/dist/index.d.ts +9 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -13
- package/dist/index.js.map +1 -1
- package/dist/lib/lexicon.d.ts +2 -0
- package/dist/lib/lexicon.d.ts.map +1 -0
- package/dist/lib/lexicon.js +3 -0
- package/dist/lib/lexicon.js.map +1 -0
- package/dist/lib/mime.d.ts +1 -1
- package/dist/lib/mime.d.ts.map +1 -1
- package/dist/lib/mime.js +2 -0
- package/dist/lib/mime.js.map +1 -1
- package/dist/lib/nsid.d.ts +2 -2
- package/dist/lib/nsid.d.ts.map +1 -1
- package/dist/lib/nsid.js +4 -6
- package/dist/lib/nsid.js.map +1 -1
- package/dist/lib/parser.d.ts +29 -0
- package/dist/lib/parser.d.ts.map +1 -0
- package/dist/lib/parser.js +152 -0
- package/dist/lib/parser.js.map +1 -0
- package/dist/lib/resource-permission.d.ts +10 -0
- package/dist/lib/resource-permission.d.ts.map +1 -0
- package/dist/lib/resource-permission.js +3 -0
- package/dist/lib/resource-permission.js.map +1 -0
- package/dist/lib/syntax-lexicon.d.ts +26 -0
- package/dist/lib/syntax-lexicon.d.ts.map +1 -0
- package/dist/lib/syntax-lexicon.js +58 -0
- package/dist/lib/syntax-lexicon.js.map +1 -0
- package/dist/lib/syntax-string.d.ts +16 -0
- package/dist/lib/syntax-string.d.ts.map +1 -0
- package/dist/lib/syntax-string.js +121 -0
- package/dist/lib/syntax-string.js.map +1 -0
- package/dist/lib/syntax.d.ts +23 -0
- package/dist/lib/syntax.d.ts.map +1 -0
- package/dist/lib/syntax.js +22 -0
- package/dist/lib/syntax.js.map +1 -0
- package/dist/lib/util.d.ts +4 -1
- package/dist/lib/util.d.ts.map +1 -1
- package/dist/lib/util.js +4 -12
- package/dist/lib/util.js.map +1 -1
- package/dist/scope-permissions-transition.d.ts +15 -0
- package/dist/scope-permissions-transition.d.ts.map +1 -0
- package/dist/{permission-set-transition.js → scope-permissions-transition.js} +5 -5
- package/dist/scope-permissions-transition.js.map +1 -0
- package/dist/scope-permissions.d.ts +22 -0
- package/dist/scope-permissions.d.ts.map +1 -0
- package/dist/{permission-set.js → scope-permissions.js} +20 -16
- package/dist/scope-permissions.js.map +1 -0
- package/dist/scopes/account-permission.d.ts +35 -0
- package/dist/scopes/account-permission.d.ts.map +1 -0
- package/dist/scopes/account-permission.js +71 -0
- package/dist/scopes/account-permission.js.map +1 -0
- package/dist/scopes/blob-permission.d.ts +27 -0
- package/dist/scopes/blob-permission.d.ts.map +1 -0
- package/dist/scopes/blob-permission.js +86 -0
- package/dist/scopes/blob-permission.js.map +1 -0
- package/dist/scopes/identity-permission.d.ts +25 -0
- package/dist/scopes/identity-permission.d.ts.map +1 -0
- package/dist/scopes/identity-permission.js +53 -0
- package/dist/scopes/identity-permission.js.map +1 -0
- package/dist/scopes/include-scope.d.ts +54 -0
- package/dist/scopes/include-scope.d.ts.map +1 -0
- package/dist/scopes/include-scope.js +156 -0
- package/dist/scopes/include-scope.js.map +1 -0
- package/dist/scopes/repo-permission.d.ts +40 -0
- package/dist/scopes/repo-permission.d.ts.map +1 -0
- package/dist/scopes/repo-permission.js +101 -0
- package/dist/scopes/repo-permission.js.map +1 -0
- package/dist/scopes/rpc-permission.d.ts +38 -0
- package/dist/scopes/rpc-permission.d.ts.map +1 -0
- package/dist/scopes/rpc-permission.js +81 -0
- package/dist/scopes/rpc-permission.js.map +1 -0
- package/dist/scopes-set.d.ts +12 -1
- package/dist/scopes-set.d.ts.map +1 -1
- package/dist/scopes-set.js +49 -3
- package/dist/scopes-set.js.map +1 -1
- package/package.json +7 -3
- package/src/atproto-oauth-scope.ts +43 -0
- package/src/index.ts +10 -14
- package/src/lib/lexicon.ts +1 -0
- package/src/lib/mime.ts +2 -1
- package/src/lib/nsid.ts +5 -6
- package/src/lib/parser.ts +176 -0
- package/src/lib/resource-permission.ts +10 -0
- package/src/lib/syntax-lexicon.ts +55 -0
- package/src/lib/syntax-string.test.ts +130 -0
- package/src/lib/syntax-string.ts +132 -0
- package/src/lib/syntax.test.ts +43 -0
- package/src/lib/syntax.ts +47 -0
- package/src/lib/util.ts +7 -12
- package/src/{permission-set-transition.test.ts → scope-permissions-transition.test.ts} +33 -20
- package/src/{permission-set-transition.ts → scope-permissions-transition.ts} +11 -11
- package/src/{permission-set.test.ts → scope-permissions.test.ts} +77 -35
- package/src/scope-permissions.ts +91 -0
- package/src/{resources/account-scope.test.ts → scopes/account-permission.test.ts} +45 -33
- package/src/scopes/account-permission.ts +75 -0
- package/src/{resources/blob-scope.test.ts → scopes/blob-permission.test.ts} +31 -23
- package/src/scopes/blob-permission.ts +105 -0
- package/src/{resources/identity-scope.test.ts → scopes/identity-permission.test.ts} +13 -13
- package/src/scopes/identity-permission.ts +54 -0
- package/src/scopes/include-scope.test.ts +626 -0
- package/src/scopes/include-scope.ts +168 -0
- package/src/{resources/repo-scope.test.ts → scopes/repo-permission.test.ts} +77 -65
- package/src/scopes/repo-permission.ts +111 -0
- package/src/scopes/rpc-permission.test.ts +323 -0
- package/src/scopes/rpc-permission.ts +85 -0
- package/src/scopes-set.test.ts +5 -5
- package/src/scopes-set.ts +79 -5
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +1 -1
- package/dist/lib/did.d.ts +0 -3
- package/dist/lib/did.d.ts.map +0 -1
- package/dist/lib/did.js +0 -6
- package/dist/lib/did.js.map +0 -1
- package/dist/parser.d.ts +0 -31
- package/dist/parser.d.ts.map +0 -1
- package/dist/parser.js +0 -118
- package/dist/parser.js.map +0 -1
- package/dist/permission-set-transition.d.ts +0 -15
- package/dist/permission-set-transition.d.ts.map +0 -1
- package/dist/permission-set-transition.js.map +0 -1
- package/dist/permission-set.d.ts +0 -22
- package/dist/permission-set.d.ts.map +0 -1
- package/dist/permission-set.js.map +0 -1
- package/dist/resources/account-scope.d.ts +0 -35
- package/dist/resources/account-scope.d.ts.map +0 -1
- package/dist/resources/account-scope.js +0 -60
- package/dist/resources/account-scope.js.map +0 -1
- package/dist/resources/blob-scope.d.ts +0 -25
- package/dist/resources/blob-scope.d.ts.map +0 -1
- package/dist/resources/blob-scope.js +0 -74
- package/dist/resources/blob-scope.js.map +0 -1
- package/dist/resources/identity-scope.d.ts +0 -25
- package/dist/resources/identity-scope.d.ts.map +0 -1
- package/dist/resources/identity-scope.js +0 -46
- package/dist/resources/identity-scope.js.map +0 -1
- package/dist/resources/repo-scope.d.ts +0 -37
- package/dist/resources/repo-scope.d.ts.map +0 -1
- package/dist/resources/repo-scope.js +0 -92
- package/dist/resources/repo-scope.js.map +0 -1
- package/dist/resources/rpc-scope.d.ts +0 -31
- package/dist/resources/rpc-scope.d.ts.map +0 -1
- package/dist/resources/rpc-scope.js +0 -74
- package/dist/resources/rpc-scope.js.map +0 -1
- package/dist/syntax.d.ts +0 -76
- package/dist/syntax.d.ts.map +0 -1
- package/dist/syntax.js +0 -249
- package/dist/syntax.js.map +0 -1
- package/dist/utilities.d.ts +0 -17
- package/dist/utilities.d.ts.map +0 -1
- package/dist/utilities.js +0 -108
- package/dist/utilities.js.map +0 -1
- package/src/lib/did.ts +0 -3
- package/src/parser.ts +0 -150
- package/src/permission-set.ts +0 -78
- package/src/resources/account-scope.ts +0 -66
- package/src/resources/blob-scope.ts +0 -86
- package/src/resources/identity-scope.ts +0 -49
- package/src/resources/repo-scope.ts +0 -101
- package/src/resources/rpc-scope.test.ts +0 -280
- package/src/resources/rpc-scope.ts +0 -77
- package/src/syntax.test.ts +0 -203
- package/src/syntax.ts +0 -325
- package/src/utilities.ts +0 -109
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import { RpcScope } from './rpc-scope.js'
|
|
2
|
-
|
|
3
|
-
describe('RpcScope', () => {
|
|
4
|
-
describe('static', () => {
|
|
5
|
-
describe('fromString', () => {
|
|
6
|
-
it('should parse positional scope', () => {
|
|
7
|
-
const scope = RpcScope.fromString(
|
|
8
|
-
'rpc:com.example.service?aud=did:example:123',
|
|
9
|
-
)
|
|
10
|
-
expect(scope).not.toBeNull()
|
|
11
|
-
expect(scope!.aud).toBe('did:example:123')
|
|
12
|
-
expect(scope!.lxm).toEqual(['com.example.service'])
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it('should parse strings correctly', () => {
|
|
16
|
-
expect(
|
|
17
|
-
RpcScope.fromString('rpc?lxm=com.example.method1&aud=*'),
|
|
18
|
-
).toEqual({
|
|
19
|
-
aud: '*',
|
|
20
|
-
lxm: ['com.example.method1'],
|
|
21
|
-
})
|
|
22
|
-
expect(RpcScope.fromString('rpc:com.example.method1?aud=*')).toEqual({
|
|
23
|
-
aud: '*',
|
|
24
|
-
lxm: ['com.example.method1'],
|
|
25
|
-
})
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('should render strings correctly', () => {
|
|
29
|
-
expect(
|
|
30
|
-
new RpcScope('did:example:123', ['com.example.service']).toString(),
|
|
31
|
-
).toBe('rpc:com.example.service?aud=did:example:123')
|
|
32
|
-
expect(new RpcScope('*', ['com.example.method1']).toString()).toBe(
|
|
33
|
-
'rpc:com.example.method1?aud=*',
|
|
34
|
-
)
|
|
35
|
-
expect(
|
|
36
|
-
new RpcScope('did:example:123', [
|
|
37
|
-
'com.example.method1',
|
|
38
|
-
'com.example.method2',
|
|
39
|
-
]).toString(),
|
|
40
|
-
).toBe(
|
|
41
|
-
'rpc?lxm=com.example.method1&lxm=com.example.method2&aud=did:example:123',
|
|
42
|
-
)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('should reject scopes without lxm', () => {
|
|
46
|
-
expect(RpcScope.fromString('rpc?aud=did:example:123')).toBeNull()
|
|
47
|
-
expect(RpcScope.fromString('rpc:?aud=did:example:123')).toBeNull()
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('should reject scopes without aud', () => {
|
|
51
|
-
expect(RpcScope.fromString('rpc?lxm=com.example.method1')).toBeNull()
|
|
52
|
-
expect(RpcScope.fromString('rpc:com.example.method1')).toBeNull()
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('should reject scopes with lxm in both positional and query form', () => {
|
|
56
|
-
expect(
|
|
57
|
-
RpcScope.fromString(
|
|
58
|
-
'rpc:com.example.method1?aud=did:example:123&lxm=com.example.method2',
|
|
59
|
-
),
|
|
60
|
-
).toBeNull()
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('should parse valid rpc scope with multiple lxm', () => {
|
|
64
|
-
const scope = RpcScope.fromString(
|
|
65
|
-
'rpc?aud=*&lxm=com.example.method1&lxm=com.example.method2',
|
|
66
|
-
)
|
|
67
|
-
expect(scope).not.toBeNull()
|
|
68
|
-
expect(scope!.aud).toBe('*')
|
|
69
|
-
expect(scope!.lxm).toEqual([
|
|
70
|
-
'com.example.method1',
|
|
71
|
-
'com.example.method2',
|
|
72
|
-
])
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('should reject rpc scope without lxm', () => {
|
|
76
|
-
const scope = RpcScope.fromString('rpc?aud=did:example:123')
|
|
77
|
-
expect(scope).toBeNull()
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('should reject rpc scope without aud', () => {
|
|
81
|
-
const scope = RpcScope.fromString('rpc?lxm=com.example.method1')
|
|
82
|
-
expect(scope).toBeNull()
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('should reject any aud/any lxm', () => {
|
|
86
|
-
expect(RpcScope.fromString('rpc?aud=*&lxm=*')).toBeNull()
|
|
87
|
-
expect(RpcScope.fromString('rpc:*?aud=*')).toBeNull()
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('should reject missing aud', () => {
|
|
91
|
-
expect(RpcScope.fromString('rpc:com.example.service')).toBeNull()
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('should reject invalid aud', () => {
|
|
95
|
-
expect(
|
|
96
|
-
RpcScope.fromString('rpc:com.example.service?aud=invalid'),
|
|
97
|
-
).toBeNull()
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('should reject invalid lxm', () => {
|
|
101
|
-
expect(RpcScope.fromString('rpc:invalid')).toBeNull()
|
|
102
|
-
expect(RpcScope.fromString('rpc?lxm=invalid')).toBeNull()
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
for (const invalid of [
|
|
106
|
-
'rpc:*',
|
|
107
|
-
'invalid',
|
|
108
|
-
'rpc:invalid',
|
|
109
|
-
'rpc:invalid?aud=did:foo:bar',
|
|
110
|
-
'rpc:foo.bar?aud=did:foo:bar&lxm=bar.baz',
|
|
111
|
-
'rpc:foo.bar?aud=invalid',
|
|
112
|
-
'rpc:invalid?aud=did:example:123',
|
|
113
|
-
'rpc:com.example.service?aud=invalid',
|
|
114
|
-
'notrpc:com.example.service?aud=did:example:123',
|
|
115
|
-
'rpc?lxm=invalid&aud=invalid',
|
|
116
|
-
]) {
|
|
117
|
-
it(`should return null for invalid rpc scope: ${invalid}`, () => {
|
|
118
|
-
expect(RpcScope.fromString(invalid)).toBeNull()
|
|
119
|
-
})
|
|
120
|
-
}
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
describe('scopeNeededFor', () => {
|
|
124
|
-
it('should return correct scope string for specific lxm and aud', () => {
|
|
125
|
-
const scope = RpcScope.scopeNeededFor({
|
|
126
|
-
lxm: 'com.example.service',
|
|
127
|
-
aud: 'did:example:123',
|
|
128
|
-
})
|
|
129
|
-
expect(scope).toBe('rpc:com.example.service?aud=did:example:123')
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('should return scope that accepts all aud with specific lxm', () => {
|
|
133
|
-
const scope = RpcScope.scopeNeededFor({
|
|
134
|
-
lxm: 'com.example.method1',
|
|
135
|
-
aud: '*',
|
|
136
|
-
})
|
|
137
|
-
expect(scope).toBe('rpc:com.example.method1?aud=*')
|
|
138
|
-
})
|
|
139
|
-
})
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
describe('instance', () => {
|
|
143
|
-
describe('matches', () => {
|
|
144
|
-
it('should match exact lxm and aud', () => {
|
|
145
|
-
const scope = RpcScope.fromString(
|
|
146
|
-
'rpc:com.example.service?aud=did:example:123',
|
|
147
|
-
)
|
|
148
|
-
expect(scope).not.toBeNull()
|
|
149
|
-
expect(
|
|
150
|
-
scope!.matches({
|
|
151
|
-
lxm: 'com.example.service',
|
|
152
|
-
aud: 'did:example:123',
|
|
153
|
-
}),
|
|
154
|
-
).toBe(true)
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it('should not match different lxm', () => {
|
|
158
|
-
const scope = RpcScope.fromString(
|
|
159
|
-
'rpc:com.example.service?aud=did:example:123',
|
|
160
|
-
)
|
|
161
|
-
expect(scope).not.toBeNull()
|
|
162
|
-
expect(
|
|
163
|
-
scope!.matches({
|
|
164
|
-
lxm: 'com.example.OtherService',
|
|
165
|
-
aud: 'did:example:123',
|
|
166
|
-
}),
|
|
167
|
-
).toBe(false)
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
it('should not match different aud', () => {
|
|
171
|
-
const scope = RpcScope.fromString(
|
|
172
|
-
'rpc:com.example.service?aud=did:example:123',
|
|
173
|
-
)
|
|
174
|
-
expect(scope).not.toBeNull()
|
|
175
|
-
expect(
|
|
176
|
-
scope!.matches({
|
|
177
|
-
lxm: 'com.example.service',
|
|
178
|
-
aud: 'did:example:456',
|
|
179
|
-
}),
|
|
180
|
-
).toBe(false)
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
it('should match wildcard aud', () => {
|
|
184
|
-
const scope = RpcScope.fromString('rpc:com.example.method1?aud=*')
|
|
185
|
-
expect(scope).not.toBeNull()
|
|
186
|
-
expect(
|
|
187
|
-
scope!.matches({
|
|
188
|
-
lxm: 'com.example.method1',
|
|
189
|
-
aud: 'did:example:123',
|
|
190
|
-
}),
|
|
191
|
-
).toBe(true)
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
it('should match wildcard lxm', () => {
|
|
195
|
-
const scope = RpcScope.fromString('rpc:*?aud=did:example:123')
|
|
196
|
-
expect(scope).not.toBeNull()
|
|
197
|
-
expect(
|
|
198
|
-
scope!.matches({
|
|
199
|
-
lxm: 'com.example.method1',
|
|
200
|
-
aud: 'did:example:123',
|
|
201
|
-
}),
|
|
202
|
-
).toBe(true)
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
it('should not match different lxm with wildcard aud', () => {
|
|
206
|
-
const scope = RpcScope.fromString('rpc:*?aud=did:example:123')
|
|
207
|
-
expect(scope).not.toBeNull()
|
|
208
|
-
expect(
|
|
209
|
-
scope!.matches({
|
|
210
|
-
lxm: 'com.example.anyMethod',
|
|
211
|
-
aud: 'did:example:123',
|
|
212
|
-
}),
|
|
213
|
-
).toBe(true)
|
|
214
|
-
})
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
describe('toString', () => {
|
|
218
|
-
it('should format scope with lxm and aud', () => {
|
|
219
|
-
const scope = new RpcScope('did:example:123', ['com.example.service'])
|
|
220
|
-
expect(scope.toString()).toBe(
|
|
221
|
-
'rpc:com.example.service?aud=did:example:123',
|
|
222
|
-
)
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
it('should format scope with wildcard aud', () => {
|
|
226
|
-
const scope = new RpcScope('*', ['com.example.method1'])
|
|
227
|
-
expect(scope.toString()).toBe('rpc:com.example.method1?aud=*')
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
it('should format scope with wildcard lxm', () => {
|
|
231
|
-
const scope = new RpcScope('did:example:123', ['*'])
|
|
232
|
-
expect(scope.toString()).toBe('rpc:*?aud=did:example:123')
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
it('simplifies lxm if one of them is "*"', () => {
|
|
236
|
-
const scope = new RpcScope('did:example:123', [
|
|
237
|
-
'*',
|
|
238
|
-
'com.example.method1',
|
|
239
|
-
])
|
|
240
|
-
expect(scope.toString()).toBe('rpc:*?aud=did:example:123')
|
|
241
|
-
})
|
|
242
|
-
})
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
describe('consistency', () => {
|
|
246
|
-
const testCases: { input: string; expected: string }[] = [
|
|
247
|
-
{
|
|
248
|
-
input: 'rpc:com.example.service?aud=did:example:123',
|
|
249
|
-
expected: 'rpc:com.example.service?aud=did:example:123',
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
input: 'rpc?lxm=com.example.method1&lxm=com.example.method2&aud=*',
|
|
253
|
-
expected: 'rpc?lxm=com.example.method1&lxm=com.example.method2&aud=*',
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
input:
|
|
257
|
-
'rpc?lxm=com.example.method1&lxm=com.example.method2&lxm=*&aud=did:example:123',
|
|
258
|
-
expected: 'rpc:*?aud=did:example:123',
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
input: 'rpc?aud=did:example:123&lxm=com.example.service',
|
|
262
|
-
expected: 'rpc:com.example.service?aud=did:example:123',
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
input: 'rpc?lxm=com.example.method1&aud=did:example:123',
|
|
266
|
-
expected: 'rpc:com.example.method1?aud=did:example:123',
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
input: 'rpc:com.example.method1?&aud=*',
|
|
270
|
-
expected: 'rpc:com.example.method1?aud=*',
|
|
271
|
-
},
|
|
272
|
-
]
|
|
273
|
-
|
|
274
|
-
for (const { input, expected } of testCases) {
|
|
275
|
-
it(`should properly re-format ${input}`, () => {
|
|
276
|
-
expect(RpcScope.fromString(input)?.toString()).toBe(expected)
|
|
277
|
-
})
|
|
278
|
-
}
|
|
279
|
-
})
|
|
280
|
-
})
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { DIDLike, isDIDLike } from '../lib/did.js'
|
|
2
|
-
import { NSID, isNSID } from '../lib/nsid.js'
|
|
3
|
-
import { Parser } from '../parser.js'
|
|
4
|
-
import { NeRoArray, ResourceSyntax, isScopeForResource } from '../syntax.js'
|
|
5
|
-
|
|
6
|
-
const validateLxmParam = (value: string) => value === '*' || isNSID(value)
|
|
7
|
-
const validateAudParam = (value: string) => value === '*' || isDIDLike(value)
|
|
8
|
-
|
|
9
|
-
export const rpcParser = new Parser(
|
|
10
|
-
'rpc',
|
|
11
|
-
{
|
|
12
|
-
lxm: {
|
|
13
|
-
multiple: true,
|
|
14
|
-
required: true,
|
|
15
|
-
validate: validateLxmParam,
|
|
16
|
-
},
|
|
17
|
-
aud: {
|
|
18
|
-
multiple: false,
|
|
19
|
-
required: true,
|
|
20
|
-
validate: validateAudParam,
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
'lxm',
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
export type RpcScopeMatch = {
|
|
27
|
-
lxm: string
|
|
28
|
-
aud: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export class RpcScope {
|
|
32
|
-
constructor(
|
|
33
|
-
public readonly aud: '*' | DIDLike,
|
|
34
|
-
public readonly lxm: NeRoArray<'*' | NSID>,
|
|
35
|
-
) {}
|
|
36
|
-
|
|
37
|
-
matches(options: RpcScopeMatch): boolean {
|
|
38
|
-
const { aud, lxm } = this
|
|
39
|
-
return (
|
|
40
|
-
(aud === '*' || aud === options.aud) &&
|
|
41
|
-
(lxm.includes('*') || (lxm as readonly string[]).includes(options.lxm))
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
toString(): string {
|
|
46
|
-
const { lxm, aud } = this
|
|
47
|
-
return rpcParser.format({
|
|
48
|
-
aud,
|
|
49
|
-
lxm: lxm.includes('*')
|
|
50
|
-
? ['*']
|
|
51
|
-
: ([...new Set(lxm)].sort() as [NSID, ...NSID[]]),
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
static fromString(scope: string): RpcScope | null {
|
|
56
|
-
if (!isScopeForResource(scope, 'rpc')) return null
|
|
57
|
-
const syntax = ResourceSyntax.fromString(scope)
|
|
58
|
-
return this.fromSyntax(syntax)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
static fromSyntax(syntax: ResourceSyntax): RpcScope | null {
|
|
62
|
-
const result = rpcParser.parse(syntax)
|
|
63
|
-
if (!result) return null
|
|
64
|
-
|
|
65
|
-
// rpc:*?aud=* is forbidden
|
|
66
|
-
if (result.aud === '*' && result.lxm.includes('*')) return null
|
|
67
|
-
|
|
68
|
-
return new RpcScope(result.aud, result.lxm)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
static scopeNeededFor(options: RpcScopeMatch): string {
|
|
72
|
-
return rpcParser.format({
|
|
73
|
-
aud: options.aud as DIDLike,
|
|
74
|
-
lxm: [options.lxm as NSID],
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
}
|
package/src/syntax.test.ts
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import { ResourceSyntax, isScopeForResource } from './syntax.js'
|
|
2
|
-
|
|
3
|
-
describe('isScopeForResource', () => {
|
|
4
|
-
describe('exact match', () => {
|
|
5
|
-
it('should return true for exact match', () => {
|
|
6
|
-
expect(isScopeForResource('resource', 'resource')).toBe(true)
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
it('should return false for different resource', () => {
|
|
10
|
-
expect(isScopeForResource('resource', 'differentResource')).toBe(false)
|
|
11
|
-
})
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
describe('with positional parameter', () => {
|
|
15
|
-
it('should return true for exact match with positional parameter', () => {
|
|
16
|
-
expect(isScopeForResource('resource:positional', 'resource')).toBe(true)
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('should return false for different resource with positional parameter', () => {
|
|
20
|
-
expect(
|
|
21
|
-
isScopeForResource('differentResource:positional', 'resource'),
|
|
22
|
-
).toBe(false)
|
|
23
|
-
})
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
describe('with named parameters', () => {
|
|
27
|
-
it('should return true for exact match with named parameters', () => {
|
|
28
|
-
expect(isScopeForResource('resource?param=value', 'resource')).toBe(true)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('should return false for different resource with named parameters', () => {
|
|
32
|
-
expect(
|
|
33
|
-
isScopeForResource('differentResource?param=value', 'resource'),
|
|
34
|
-
).toBe(false)
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
for (const { scope, normalized = scope, content } of [
|
|
40
|
-
{
|
|
41
|
-
scope: 'my-res',
|
|
42
|
-
content: { resource: 'my-res' },
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
scope: 'my-res:my-pos',
|
|
46
|
-
content: { resource: 'my-res', positional: 'my-pos' },
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
scope: 'my-res:',
|
|
50
|
-
content: { resource: 'my-res', positional: '' },
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
scope: 'my-res:foo?x=value&y=value-y',
|
|
54
|
-
content: {
|
|
55
|
-
resource: 'my-res',
|
|
56
|
-
positional: 'foo',
|
|
57
|
-
params: { x: ['value'], y: ['value-y'] },
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
scope: 'my-res?x=value&y=value-y',
|
|
62
|
-
content: { resource: 'my-res', params: { x: ['value'], y: ['value-y'] } },
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
scope: 'my-res?x=foo&x=bar&x=baz',
|
|
66
|
-
content: { resource: 'my-res', params: { x: ['foo', 'bar', 'baz'] } },
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
scope: 'rpc:foo.bar?aud=did:foo:bar?lxm=bar.baz',
|
|
70
|
-
normalized: 'rpc:foo.bar?aud=did:foo:bar%3Flxm%3Dbar.baz',
|
|
71
|
-
content: {
|
|
72
|
-
resource: 'rpc',
|
|
73
|
-
positional: 'foo.bar',
|
|
74
|
-
params: { aud: ['did:foo:bar?lxm=bar.baz'] },
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
] as Array<{
|
|
78
|
-
scope: string
|
|
79
|
-
normalized?: string
|
|
80
|
-
content: {
|
|
81
|
-
resource: string
|
|
82
|
-
positional?: string
|
|
83
|
-
params?: Record<string, string[]>
|
|
84
|
-
}
|
|
85
|
-
}>) {
|
|
86
|
-
describe(`Valid "${scope}"`, () => {
|
|
87
|
-
const syntax = ResourceSyntax.fromString(scope)
|
|
88
|
-
|
|
89
|
-
it('should match the expected syntax', () => {
|
|
90
|
-
expect(syntax).toEqual({
|
|
91
|
-
resource: content.resource,
|
|
92
|
-
positional: content.positional,
|
|
93
|
-
params: content.params
|
|
94
|
-
? new URLSearchParams(
|
|
95
|
-
Object.entries(content.params).flatMap(([k, v]) =>
|
|
96
|
-
v.map((val) => [k, val]),
|
|
97
|
-
),
|
|
98
|
-
)
|
|
99
|
-
: undefined,
|
|
100
|
-
})
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
it(`should stringify ${scope} correctly`, () => {
|
|
104
|
-
expect(syntax.toString()).toBe(normalized)
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it(`should parse ${scope} correctly`, () => {
|
|
108
|
-
expect(syntax.toJSON()).toMatchObject(content)
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it(`should match ${scope} resource`, () => {
|
|
112
|
-
expect(syntax.is(content.resource)).toBe(true)
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
it(`should return undefined for nonexistent single-value param`, () => {
|
|
116
|
-
expect(syntax.getSingle('nonexistent')).toBeUndefined()
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it(`should return undefined for nonexistent multi-value param`, () => {
|
|
120
|
-
expect(syntax.getMulti('nonexistent')).toBeUndefined()
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
const { params } = content
|
|
124
|
-
if (params) {
|
|
125
|
-
it(`should allow detecting unknown params`, () => {
|
|
126
|
-
const allowedParams = Object.keys(params) as [string, ...string[]]
|
|
127
|
-
expect(syntax.containsParamsOtherThan(allowedParams)).toBe(false)
|
|
128
|
-
|
|
129
|
-
if (allowedParams.length > 1) {
|
|
130
|
-
const woFirst = allowedParams.slice(1) as [string, ...string[]]
|
|
131
|
-
expect(syntax.containsParamsOtherThan(woFirst)).toBe(true)
|
|
132
|
-
|
|
133
|
-
const woLast = allowedParams.slice(0, -1) as [string, ...string[]]
|
|
134
|
-
expect(syntax.containsParamsOtherThan(woLast)).toBe(true)
|
|
135
|
-
}
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
for (const [key, values] of Object.entries(params)) {
|
|
139
|
-
it(`should get an array when reading "${key}"`, () => {
|
|
140
|
-
expect(syntax.getMulti(key)).toEqual(values)
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
if (values.length === 1) {
|
|
144
|
-
it(`should allow retrieving single-value params`, () => {
|
|
145
|
-
expect(syntax.getSingle(key)).toEqual(values[0])
|
|
146
|
-
})
|
|
147
|
-
} else {
|
|
148
|
-
it(`should return null for multi-value params`, () => {
|
|
149
|
-
expect(syntax.getSingle(key)).toBeNull()
|
|
150
|
-
expect(syntax.getSingle(key, true)).toBeNull()
|
|
151
|
-
})
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const { positional } = content
|
|
157
|
-
if (positional !== undefined) {
|
|
158
|
-
it(`should return positional parameter`, () => {
|
|
159
|
-
expect(syntax.positional).toBe(positional)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it(`should return positional parameter when reading as single-value`, () => {
|
|
163
|
-
expect(syntax.getSingle('nonexistent', true)).toBe(positional)
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
it(`should return positional parameter when reading as multi-value`, () => {
|
|
167
|
-
expect(syntax.getMulti('nonexistent', true)).toEqual([positional])
|
|
168
|
-
})
|
|
169
|
-
}
|
|
170
|
-
})
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
describe('invalid positional parameters', () => {
|
|
174
|
-
it('should return null for positional parameters used together with named parameters', () => {
|
|
175
|
-
const syntax = ResourceSyntax.fromString('my-res:pos?x=value')
|
|
176
|
-
expect(syntax.getSingle('x', true)).toBeNull()
|
|
177
|
-
expect(syntax.getMulti('x', true)).toBeNull()
|
|
178
|
-
})
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
describe('containsParamsOtherThan', () => {
|
|
182
|
-
it('should return true if there are additional params', () => {
|
|
183
|
-
const syntax = ResourceSyntax.fromString('my-res?x=value&y=value-y')
|
|
184
|
-
expect(syntax.containsParamsOtherThan(['x'])).toBe(true)
|
|
185
|
-
})
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
describe('url encoding', () => {
|
|
189
|
-
it('should handle URL encoding in positional parameters', () => {
|
|
190
|
-
const syntax = ResourceSyntax.fromString('my-res:my%20pos')
|
|
191
|
-
expect(syntax.positional).toBe('my pos')
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
it('should handle URL encoding in named parameters', () => {
|
|
195
|
-
const syntax = ResourceSyntax.fromString('my-res?x=my%20value')
|
|
196
|
-
expect(syntax.getSingle('x')).toBe('my value')
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
it(`should allow colon (:) in positional parameters`, () => {
|
|
200
|
-
const syntax = ResourceSyntax.fromString('my-res:my:pos')
|
|
201
|
-
expect(syntax.positional).toBe('my:pos')
|
|
202
|
-
})
|
|
203
|
-
})
|