@atproto/oauth-scopes 0.5.2 → 0.5.3
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 +12 -0
- package/package.json +10 -6
- package/jest.config.cjs +0 -14
- package/src/atproto-oauth-scope.ts +0 -79
- package/src/index.ts +0 -13
- package/src/lib/lexicon.ts +0 -21
- package/src/lib/mime.test.ts +0 -98
- package/src/lib/mime.ts +0 -71
- package/src/lib/nsid.ts +0 -5
- package/src/lib/parser.ts +0 -176
- package/src/lib/resource-permission.ts +0 -10
- package/src/lib/syntax-lexicon.ts +0 -55
- package/src/lib/syntax-string.test.ts +0 -130
- package/src/lib/syntax-string.ts +0 -132
- package/src/lib/syntax.test.ts +0 -43
- package/src/lib/syntax.ts +0 -54
- package/src/lib/util.ts +0 -18
- package/src/scope-missing-error.ts +0 -15
- package/src/scope-permissions-transition.test.ts +0 -122
- package/src/scope-permissions-transition.ts +0 -71
- package/src/scope-permissions.test.ts +0 -303
- package/src/scope-permissions.ts +0 -91
- package/src/scopes/account-permission.test.ts +0 -187
- package/src/scopes/account-permission.ts +0 -78
- package/src/scopes/blob-permission.test.ts +0 -126
- package/src/scopes/blob-permission.ts +0 -105
- package/src/scopes/identity-permission.test.ts +0 -80
- package/src/scopes/identity-permission.ts +0 -54
- package/src/scopes/include-scope.test.ts +0 -637
- package/src/scopes/include-scope.ts +0 -208
- package/src/scopes/repo-permission.test.ts +0 -267
- package/src/scopes/repo-permission.ts +0 -111
- package/src/scopes/rpc-permission.test.ts +0 -323
- package/src/scopes/rpc-permission.ts +0 -90
- package/src/scopes-set.test.ts +0 -47
- package/src/scopes-set.ts +0 -134
- package/tsconfig.build.json +0 -9
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -7
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { BlobPermission } from './blob-permission.js'
|
|
2
|
-
|
|
3
|
-
describe('BlobPermission', () => {
|
|
4
|
-
describe('static', () => {
|
|
5
|
-
describe('fromString', () => {
|
|
6
|
-
it('should parse positional scope', () => {
|
|
7
|
-
const scope = BlobPermission.fromString('blob:image/png')
|
|
8
|
-
expect(scope).not.toBeNull()
|
|
9
|
-
expect(scope!.accept).toEqual(['image/png'])
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('should parse valid blob scope with multiple accept parameters', () => {
|
|
13
|
-
const scope = BlobPermission.fromString(
|
|
14
|
-
'blob?accept=image/png&accept=image/jpeg',
|
|
15
|
-
)
|
|
16
|
-
expect(scope).not.toBeNull()
|
|
17
|
-
expect(scope!.accept).toEqual(['image/png', 'image/jpeg'])
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('should reject blob scope without accept', () => {
|
|
21
|
-
const scope = BlobPermission.fromString('blob')
|
|
22
|
-
expect(scope).toBeNull()
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
for (const invalid of [
|
|
26
|
-
'invalid',
|
|
27
|
-
'scope',
|
|
28
|
-
'blob:invalid',
|
|
29
|
-
'blob?accept=invalid-mime',
|
|
30
|
-
'blob?accept=invalid',
|
|
31
|
-
'blob:*/**',
|
|
32
|
-
'blob:*/png',
|
|
33
|
-
]) {
|
|
34
|
-
it(`should return null for invalid rpc scope: ${invalid}`, () => {
|
|
35
|
-
expect(BlobPermission.fromString(invalid)).toBeNull()
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
describe('scopeNeededFor', () => {
|
|
41
|
-
it('should return correct scope string for specific MIME type', () => {
|
|
42
|
-
const scope = BlobPermission.scopeNeededFor({ mime: 'image/png' })
|
|
43
|
-
expect(scope).toBe('blob:image/png')
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('should return scope that accepts all MIME types', () => {
|
|
47
|
-
const scope = BlobPermission.scopeNeededFor({
|
|
48
|
-
mime: 'application/json',
|
|
49
|
-
})
|
|
50
|
-
expect(scope).toBe('blob:application/json')
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
describe('instance', () => {
|
|
56
|
-
describe('matches', () => {
|
|
57
|
-
it('should match exact MIME type', () => {
|
|
58
|
-
const scope = BlobPermission.fromString('blob:image/png')
|
|
59
|
-
expect(scope).not.toBeNull()
|
|
60
|
-
expect(scope!.matches({ mime: 'image/png' })).toBe(true)
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('should match wildcard MIME type', () => {
|
|
64
|
-
const scope = BlobPermission.fromString('blob:*/*')
|
|
65
|
-
expect(scope).not.toBeNull()
|
|
66
|
-
expect(scope!.matches({ mime: 'image/jpeg' })).toBe(true)
|
|
67
|
-
expect(scope!.matches({ mime: 'application/json' })).toBe(true)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('should match subtype wildcard MIME type', () => {
|
|
71
|
-
const scope = BlobPermission.fromString('blob:image/*')
|
|
72
|
-
expect(scope).not.toBeNull()
|
|
73
|
-
expect(scope!.matches({ mime: 'image/gif' })).toBe(true)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('should not match different MIME type', () => {
|
|
77
|
-
const scope = BlobPermission.fromString('blob:image/png')
|
|
78
|
-
expect(scope).not.toBeNull()
|
|
79
|
-
expect(scope!.matches({ mime: 'image/jpeg' })).toBe(false)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('should match multiple accept values', () => {
|
|
83
|
-
const scope = BlobPermission.fromString(
|
|
84
|
-
'blob?accept=image/png&accept=image/jpeg',
|
|
85
|
-
)
|
|
86
|
-
expect(scope).not.toBeNull()
|
|
87
|
-
expect(scope!.matches({ mime: 'image/png' })).toBe(true)
|
|
88
|
-
expect(scope!.matches({ mime: 'image/jpeg' })).toBe(true)
|
|
89
|
-
expect(scope!.matches({ mime: 'image/gif' })).toBe(false)
|
|
90
|
-
})
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
describe('toString', () => {
|
|
94
|
-
it('should format scope with accept parameter', () => {
|
|
95
|
-
const scope = new BlobPermission(['image/png', 'image/jpeg'])
|
|
96
|
-
expect(scope.toString()).toBe('blob?accept=image/jpeg&accept=image/png')
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
it('should strip redundant accept parameters', () => {
|
|
100
|
-
expect(new BlobPermission(['*/*', 'image/*']).toString()).toBe(
|
|
101
|
-
'blob:*/*',
|
|
102
|
-
)
|
|
103
|
-
expect(new BlobPermission(['*/*', 'image/png']).toString()).toBe(
|
|
104
|
-
'blob:*/*',
|
|
105
|
-
)
|
|
106
|
-
expect(new BlobPermission(['image/*', 'image/png']).toString()).toBe(
|
|
107
|
-
'blob:image/*',
|
|
108
|
-
)
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('should use positional format for single accept', () => {
|
|
112
|
-
expect(new BlobPermission(['image/png']).toString()).toBe(
|
|
113
|
-
'blob:image/png',
|
|
114
|
-
)
|
|
115
|
-
expect(new BlobPermission(['image/*']).toString()).toBe('blob:image/*')
|
|
116
|
-
expect(new BlobPermission(['*/*']).toString()).toBe('blob:*/*')
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it('should use query format for multiple accepts', () => {
|
|
120
|
-
expect(new BlobPermission(['image/png', 'image/jpeg']).toString()).toBe(
|
|
121
|
-
'blob?accept=image/jpeg&accept=image/png',
|
|
122
|
-
)
|
|
123
|
-
})
|
|
124
|
-
})
|
|
125
|
-
})
|
|
126
|
-
})
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { Accept, isAccept, matchesAnyAccept } from '../lib/mime.js'
|
|
2
|
-
import { Parser } from '../lib/parser.js'
|
|
3
|
-
import { ResourcePermission } from '../lib/resource-permission.js'
|
|
4
|
-
import { ScopeStringSyntax } from '../lib/syntax-string.js'
|
|
5
|
-
import {
|
|
6
|
-
NeArray,
|
|
7
|
-
NeRoArray,
|
|
8
|
-
ParamValue,
|
|
9
|
-
ScopeSyntax,
|
|
10
|
-
isScopeStringFor,
|
|
11
|
-
} from '../lib/syntax.js'
|
|
12
|
-
|
|
13
|
-
export { type Accept }
|
|
14
|
-
|
|
15
|
-
export const DEFAULT_ACCEPT = Object.freeze(['*/*'] as const)
|
|
16
|
-
|
|
17
|
-
export type BlobPermissionMatch = {
|
|
18
|
-
mime: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class BlobPermission
|
|
22
|
-
implements ResourcePermission<'blob', BlobPermissionMatch>
|
|
23
|
-
{
|
|
24
|
-
constructor(public readonly accept: NeRoArray<Accept>) {}
|
|
25
|
-
|
|
26
|
-
matches(options: BlobPermissionMatch) {
|
|
27
|
-
return matchesAnyAccept(this.accept, options.mime)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
toString() {
|
|
31
|
-
return BlobPermission.parser.format(this)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
protected static readonly parser = new Parser(
|
|
35
|
-
'blob',
|
|
36
|
-
{
|
|
37
|
-
accept: {
|
|
38
|
-
multiple: true,
|
|
39
|
-
required: true,
|
|
40
|
-
validate: isAccept,
|
|
41
|
-
normalize: (value) => {
|
|
42
|
-
// Returns a more concise representation of the accept values.
|
|
43
|
-
if (value.includes('*/*')) return DEFAULT_ACCEPT
|
|
44
|
-
|
|
45
|
-
return value
|
|
46
|
-
.map(toLowerCase)
|
|
47
|
-
.filter(isNonRedundant)
|
|
48
|
-
.sort() as NeArray<Accept>
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
'accept',
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
static fromString(scope: string) {
|
|
56
|
-
if (!isScopeStringFor(scope, 'blob')) return null
|
|
57
|
-
const syntax = ScopeStringSyntax.fromString(scope)
|
|
58
|
-
return BlobPermission.fromSyntax(syntax)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
static fromSyntax(syntax: ScopeSyntax<'blob'>) {
|
|
62
|
-
const result = BlobPermission.parser.parse(syntax)
|
|
63
|
-
if (!result) return null
|
|
64
|
-
|
|
65
|
-
return new BlobPermission(result.accept)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
static scopeNeededFor(options: BlobPermissionMatch) {
|
|
69
|
-
return BlobPermission.parser.format({
|
|
70
|
-
accept: [options.mime as Accept],
|
|
71
|
-
})
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function toLowerCase<T extends ParamValue>(
|
|
76
|
-
value: T,
|
|
77
|
-
): T extends string ? string : T {
|
|
78
|
-
return (
|
|
79
|
-
typeof value === 'string' ? value.toLowerCase() : value
|
|
80
|
-
) as T extends string ? string : T
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function isNonRedundant(
|
|
84
|
-
value: ParamValue,
|
|
85
|
-
index: number,
|
|
86
|
-
arr: readonly ParamValue[],
|
|
87
|
-
): boolean {
|
|
88
|
-
if (typeof value !== 'string') {
|
|
89
|
-
return true
|
|
90
|
-
}
|
|
91
|
-
if (value.endsWith('/*')) {
|
|
92
|
-
// assuming the array contains unique element, wildcards cannot be redundant
|
|
93
|
-
// with one another ('image/*' is not redundant with 'text/*')
|
|
94
|
-
return true
|
|
95
|
-
}
|
|
96
|
-
const base = value.split('/', 1)[0]
|
|
97
|
-
if (arr.includes(`${base}/*`)) {
|
|
98
|
-
// If another value in the array is a wildcard for the same base, we can
|
|
99
|
-
// skip this one as it is redundant. e.g. if the array contains 'image/png'
|
|
100
|
-
// and 'image/*', we can skip 'image/png' because 'image/*' already covers
|
|
101
|
-
// it.
|
|
102
|
-
return false
|
|
103
|
-
}
|
|
104
|
-
return true
|
|
105
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { IdentityPermission } from './identity-permission.js'
|
|
2
|
-
|
|
3
|
-
describe('IdentityPermission', () => {
|
|
4
|
-
describe('static', () => {
|
|
5
|
-
describe('fromString', () => {
|
|
6
|
-
it('should parse positional scope', () => {
|
|
7
|
-
const scope = IdentityPermission.fromString('identity:handle')
|
|
8
|
-
expect(scope).not.toBeNull()
|
|
9
|
-
expect(scope!.attr).toBe('handle')
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('should parse valid identity scope with wildcard attribute', () => {
|
|
13
|
-
const scope = IdentityPermission.fromString('identity:*')
|
|
14
|
-
expect(scope).not.toBeNull()
|
|
15
|
-
expect(scope!.attr).toBe('*')
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('should return null for invalid identity scope', () => {
|
|
19
|
-
expect(IdentityPermission.fromString('invalid')).toBeNull()
|
|
20
|
-
expect(IdentityPermission.fromString('identity:invalid')).toBeNull()
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
for (const invalid of [
|
|
24
|
-
'identity:*?action=*',
|
|
25
|
-
'identity:*?action=manage',
|
|
26
|
-
'identity:*?action=submit',
|
|
27
|
-
'invalid',
|
|
28
|
-
'identity:invalid',
|
|
29
|
-
'identity:handle?action=invalid',
|
|
30
|
-
'identity?attribute=invalid&action=invalid',
|
|
31
|
-
]) {
|
|
32
|
-
it(`should return null for invalid rpc scope: ${invalid}`, () => {
|
|
33
|
-
expect(IdentityPermission.fromString(invalid)).toBeNull()
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
describe('scopeNeededFor', () => {
|
|
39
|
-
it('should return correct scope string for specific attribute and action', () => {
|
|
40
|
-
const scope = IdentityPermission.scopeNeededFor({ attr: 'handle' })
|
|
41
|
-
expect(scope).toBe('identity:handle')
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('should return scope that accepts all attributes with specific action', () => {
|
|
45
|
-
const scope = IdentityPermission.scopeNeededFor({ attr: '*' })
|
|
46
|
-
expect(scope).toBe('identity:*')
|
|
47
|
-
})
|
|
48
|
-
})
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
describe('instance', () => {
|
|
52
|
-
describe('matches', () => {
|
|
53
|
-
it('should match default attribute and action', () => {
|
|
54
|
-
const scope = IdentityPermission.fromString('identity:handle')
|
|
55
|
-
expect(scope).not.toBeNull()
|
|
56
|
-
expect(scope!.matches({ attr: 'handle' })).toBe(true)
|
|
57
|
-
expect(scope!.matches({ attr: '*' })).toBe(false)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('should match wildcard attribute with specific action', () => {
|
|
61
|
-
const scope = IdentityPermission.fromString('identity:*')
|
|
62
|
-
expect(scope).not.toBeNull()
|
|
63
|
-
expect(scope!.matches({ attr: '*' })).toBe(true)
|
|
64
|
-
expect(scope!.matches({ attr: 'handle' })).toBe(true)
|
|
65
|
-
})
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
describe('toString', () => {
|
|
69
|
-
it('should format scope with default action', () => {
|
|
70
|
-
const scope = new IdentityPermission('handle')
|
|
71
|
-
expect(scope.toString()).toBe('identity:handle')
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('should format wildcard attribute with default action', () => {
|
|
75
|
-
const scope = new IdentityPermission('*')
|
|
76
|
-
expect(scope.toString()).toBe('identity:*')
|
|
77
|
-
})
|
|
78
|
-
})
|
|
79
|
-
})
|
|
80
|
-
})
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { Parser } from '../lib/parser.js'
|
|
2
|
-
import { ResourcePermission } from '../lib/resource-permission.js'
|
|
3
|
-
import { ScopeStringSyntax } from '../lib/syntax-string.js'
|
|
4
|
-
import { ScopeSyntax, isScopeStringFor } from '../lib/syntax.js'
|
|
5
|
-
import { knownValuesValidator } from '../lib/util.js'
|
|
6
|
-
|
|
7
|
-
export const IDENTITY_ATTRIBUTES = Object.freeze(['handle', '*'] as const)
|
|
8
|
-
export type IdentityAttribute = (typeof IDENTITY_ATTRIBUTES)[number]
|
|
9
|
-
|
|
10
|
-
export type IdentityPermissionMatch = {
|
|
11
|
-
attr: IdentityAttribute
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class IdentityPermission
|
|
15
|
-
implements ResourcePermission<'identity', IdentityPermissionMatch>
|
|
16
|
-
{
|
|
17
|
-
constructor(public readonly attr: IdentityAttribute) {}
|
|
18
|
-
|
|
19
|
-
matches(options: IdentityPermissionMatch) {
|
|
20
|
-
return this.attr === '*' || this.attr === options.attr
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
toString() {
|
|
24
|
-
return IdentityPermission.parser.format(this)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
protected static readonly parser = new Parser(
|
|
28
|
-
'identity',
|
|
29
|
-
{
|
|
30
|
-
attr: {
|
|
31
|
-
multiple: false,
|
|
32
|
-
required: true,
|
|
33
|
-
validate: knownValuesValidator(IDENTITY_ATTRIBUTES),
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
'attr',
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
static fromString(scope: string) {
|
|
40
|
-
if (!isScopeStringFor(scope, 'identity')) return null
|
|
41
|
-
const syntax = ScopeStringSyntax.fromString(scope)
|
|
42
|
-
return IdentityPermission.fromSyntax(syntax)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
static fromSyntax(syntax: ScopeSyntax<'identity'>) {
|
|
46
|
-
const result = IdentityPermission.parser.parse(syntax)
|
|
47
|
-
if (!result) return null
|
|
48
|
-
return new IdentityPermission(result.attr)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
static scopeNeededFor(options: IdentityPermissionMatch): string {
|
|
52
|
-
return IdentityPermission.parser.format(options)
|
|
53
|
-
}
|
|
54
|
-
}
|