@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
package/dist/scopes-set.js
CHANGED
|
@@ -3,7 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ScopesSet = exports.ScopeMissingError = void 0;
|
|
4
4
|
const scope_missing_error_js_1 = require("./scope-missing-error.js");
|
|
5
5
|
Object.defineProperty(exports, "ScopeMissingError", { enumerable: true, get: function () { return scope_missing_error_js_1.ScopeMissingError; } });
|
|
6
|
-
const
|
|
6
|
+
const account_permission_js_1 = require("./scopes/account-permission.js");
|
|
7
|
+
const blob_permission_js_1 = require("./scopes/blob-permission.js");
|
|
8
|
+
const identity_permission_js_1 = require("./scopes/identity-permission.js");
|
|
9
|
+
const repo_permission_js_1 = require("./scopes/repo-permission.js");
|
|
10
|
+
const rpc_permission_js_1 = require("./scopes/rpc-permission.js");
|
|
7
11
|
/**
|
|
8
12
|
* Utility class to manage a set of scopes and check if they match specific
|
|
9
13
|
* options for a given resource.
|
|
@@ -15,14 +19,14 @@ class ScopesSet extends Set {
|
|
|
15
19
|
*/
|
|
16
20
|
matches(resource, options) {
|
|
17
21
|
for (const scope of this) {
|
|
18
|
-
if ((
|
|
22
|
+
if (permissionScopeMatches(scope, resource, options))
|
|
19
23
|
return true;
|
|
20
24
|
}
|
|
21
25
|
return false;
|
|
22
26
|
}
|
|
23
27
|
assert(resource, options) {
|
|
24
28
|
if (!this.matches(resource, options)) {
|
|
25
|
-
const scope =
|
|
29
|
+
const scope = scopeNeededFor(resource, options);
|
|
26
30
|
throw new scope_missing_error_js_1.ScopeMissingError(scope);
|
|
27
31
|
}
|
|
28
32
|
}
|
|
@@ -52,4 +56,46 @@ class ScopesSet extends Set {
|
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
58
|
exports.ScopesSet = ScopesSet;
|
|
59
|
+
function scopeNeededFor(resource, options) {
|
|
60
|
+
switch (resource) {
|
|
61
|
+
case 'account':
|
|
62
|
+
return account_permission_js_1.AccountPermission.scopeNeededFor(options);
|
|
63
|
+
case 'identity':
|
|
64
|
+
return identity_permission_js_1.IdentityPermission.scopeNeededFor(options);
|
|
65
|
+
case 'repo':
|
|
66
|
+
return repo_permission_js_1.RepoPermission.scopeNeededFor(options);
|
|
67
|
+
case 'rpc':
|
|
68
|
+
return rpc_permission_js_1.RpcPermission.scopeNeededFor(options);
|
|
69
|
+
case 'blob':
|
|
70
|
+
return blob_permission_js_1.BlobPermission.scopeNeededFor(options);
|
|
71
|
+
}
|
|
72
|
+
// @ts-expect-error
|
|
73
|
+
throw new TypeError(`Unknown resource: ${resource}`);
|
|
74
|
+
}
|
|
75
|
+
function permissionScopeMatches(scope, resource, options) {
|
|
76
|
+
// @NOTE we might want to cache the parsed scopes though, in practice, a
|
|
77
|
+
// single scope is unlikely to be parsed multiple times during a single
|
|
78
|
+
// request.
|
|
79
|
+
const permission = parsePermissionScope(resource, scope);
|
|
80
|
+
if (!permission)
|
|
81
|
+
return false;
|
|
82
|
+
// @ts-expect-error
|
|
83
|
+
return permission.matches(options);
|
|
84
|
+
}
|
|
85
|
+
function parsePermissionScope(resource, scope) {
|
|
86
|
+
switch (resource) {
|
|
87
|
+
case 'account':
|
|
88
|
+
return account_permission_js_1.AccountPermission.fromString(scope);
|
|
89
|
+
case 'identity':
|
|
90
|
+
return identity_permission_js_1.IdentityPermission.fromString(scope);
|
|
91
|
+
case 'repo':
|
|
92
|
+
return repo_permission_js_1.RepoPermission.fromString(scope);
|
|
93
|
+
case 'rpc':
|
|
94
|
+
return rpc_permission_js_1.RpcPermission.fromString(scope);
|
|
95
|
+
case 'blob':
|
|
96
|
+
return blob_permission_js_1.BlobPermission.fromString(scope);
|
|
97
|
+
default:
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
55
101
|
//# sourceMappingURL=scopes-set.js.map
|
package/dist/scopes-set.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scopes-set.js","sourceRoot":"","sources":["../src/scopes-set.ts"],"names":[],"mappings":";;;AAAA,qEAA4D;
|
|
1
|
+
{"version":3,"file":"scopes-set.js","sourceRoot":"","sources":["../src/scopes-set.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/oauth-scopes",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "A library for manipulating and validating ATproto OAuth scopes in TypeScript.",
|
|
6
6
|
"keywords": [
|
|
@@ -24,13 +24,17 @@
|
|
|
24
24
|
"default": "./dist/index.js"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
|
-
"dependencies": {
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@atproto/did": "^0.2.0",
|
|
29
|
+
"@atproto/lexicon": "^0.5.0",
|
|
30
|
+
"@atproto/syntax": "^0.4.1"
|
|
31
|
+
},
|
|
28
32
|
"devDependencies": {
|
|
29
33
|
"jest": "^28.1.2",
|
|
30
34
|
"typescript": "^5.6.3"
|
|
31
35
|
},
|
|
32
36
|
"scripts": {
|
|
33
|
-
"build": "tsc --build tsconfig.json",
|
|
37
|
+
"build": "tsc --build tsconfig.build.json",
|
|
34
38
|
"test": "jest"
|
|
35
39
|
}
|
|
36
40
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ScopeStringFor, isScopeStringFor } from './lib/syntax.js'
|
|
2
|
+
import { AccountPermission } from './scopes/account-permission.js'
|
|
3
|
+
import { BlobPermission } from './scopes/blob-permission.js'
|
|
4
|
+
import { IdentityPermission } from './scopes/identity-permission.js'
|
|
5
|
+
import { IncludeScope } from './scopes/include-scope.js'
|
|
6
|
+
import { RepoPermission } from './scopes/repo-permission.js'
|
|
7
|
+
import { RpcPermission } from './scopes/rpc-permission.js'
|
|
8
|
+
|
|
9
|
+
export { type ScopeStringFor, isScopeStringFor }
|
|
10
|
+
|
|
11
|
+
export type AtprotoOauthScope =
|
|
12
|
+
| 'atproto'
|
|
13
|
+
| 'transition:email'
|
|
14
|
+
| 'transition:generic'
|
|
15
|
+
| 'transition:chat.bsky'
|
|
16
|
+
| ScopeStringFor<'account'>
|
|
17
|
+
| ScopeStringFor<'blob'>
|
|
18
|
+
| ScopeStringFor<'identity'>
|
|
19
|
+
| ScopeStringFor<'include'>
|
|
20
|
+
| ScopeStringFor<'repo'>
|
|
21
|
+
| ScopeStringFor<'rpc'>
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @note This function does not only verify the scope string format (with
|
|
25
|
+
* {@link isScopeStringFor}), but also checks if the provided parameters are
|
|
26
|
+
* valid according to the respective scope syntax definition. This allows
|
|
27
|
+
* excluding scopes that cannot be fully interpreted by the current version of
|
|
28
|
+
* the code.
|
|
29
|
+
*/
|
|
30
|
+
export function isAtprotoOauthScope(value: string): value is AtprotoOauthScope {
|
|
31
|
+
return (
|
|
32
|
+
value === 'atproto' ||
|
|
33
|
+
value === 'transition:email' ||
|
|
34
|
+
value === 'transition:generic' ||
|
|
35
|
+
value === 'transition:chat.bsky' ||
|
|
36
|
+
AccountPermission.fromString(value) != null ||
|
|
37
|
+
BlobPermission.fromString(value) != null ||
|
|
38
|
+
IdentityPermission.fromString(value) != null ||
|
|
39
|
+
IncludeScope.fromString(value) != null ||
|
|
40
|
+
RepoPermission.fromString(value) != null ||
|
|
41
|
+
RpcPermission.fromString(value) != null
|
|
42
|
+
)
|
|
43
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
|
+
export * from './atproto-oauth-scope.js'
|
|
2
|
+
|
|
1
3
|
export * from './scope-missing-error.js'
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
4
|
+
export * from './scope-permissions-transition.js'
|
|
5
|
+
export * from './scope-permissions.js'
|
|
4
6
|
export * from './scopes-set.js'
|
|
5
|
-
export * from './parser.js'
|
|
6
|
-
export * from './syntax.js'
|
|
7
|
-
export * from './utilities.js'
|
|
8
|
-
|
|
9
|
-
export * from './resources/account-scope.js'
|
|
10
|
-
export * from './resources/blob-scope.js'
|
|
11
|
-
export * from './resources/identity-scope.js'
|
|
12
|
-
export * from './resources/repo-scope.js'
|
|
13
|
-
export * from './resources/rpc-scope.js'
|
|
14
7
|
|
|
15
|
-
export * from './
|
|
16
|
-
export * from './
|
|
17
|
-
export * from './
|
|
8
|
+
export * from './scopes/account-permission.js'
|
|
9
|
+
export * from './scopes/blob-permission.js'
|
|
10
|
+
export * from './scopes/identity-permission.js'
|
|
11
|
+
export * from './scopes/include-scope.js'
|
|
12
|
+
export * from './scopes/repo-permission.js'
|
|
13
|
+
export * from './scopes/rpc-permission.js'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { LexPermission, LexPermissionSet } from '@atproto/lexicon'
|
package/src/lib/mime.ts
CHANGED
|
@@ -20,7 +20,8 @@ export function isMime(value: string): value is Mime {
|
|
|
20
20
|
|
|
21
21
|
export type Accept = '*/*' | `${string}/*` | Mime
|
|
22
22
|
|
|
23
|
-
export function isAccept(value:
|
|
23
|
+
export function isAccept(value: unknown): value is Accept {
|
|
24
|
+
if (typeof value !== 'string') return false
|
|
24
25
|
if (value === '*/*') return true // Fast path for the most common case
|
|
25
26
|
if (!isStringSlashString(value)) return false
|
|
26
27
|
return !value.includes('*') || value.endsWith('/*')
|
package/src/lib/nsid.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
!value.endsWith('.')
|
|
1
|
+
import { isValidNsid } from '@atproto/syntax'
|
|
2
|
+
|
|
3
|
+
export type Nsid = `${string}.${string}.${string}`
|
|
4
|
+
export const isNsid = (v: unknown): v is Nsid =>
|
|
5
|
+
typeof v === 'string' && isValidNsid(v)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { ScopeStringSyntax } from './syntax-string.js'
|
|
2
|
+
import { NeRoArray, ParamValue, ScopeSyntax } from './syntax.js'
|
|
3
|
+
|
|
4
|
+
type InferParamPredicate<T extends (value: ParamValue) => boolean> =
|
|
5
|
+
T extends ((value: ParamValue) => value is infer U extends ParamValue)
|
|
6
|
+
? U
|
|
7
|
+
: ParamValue
|
|
8
|
+
|
|
9
|
+
type ParamsSchema = Record<
|
|
10
|
+
string,
|
|
11
|
+
| {
|
|
12
|
+
multiple: false
|
|
13
|
+
required: boolean
|
|
14
|
+
default?: ParamValue
|
|
15
|
+
normalize?: (value: ParamValue) => ParamValue
|
|
16
|
+
validate: (value: ParamValue) => boolean
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
multiple: true
|
|
20
|
+
required: boolean
|
|
21
|
+
default?: NeRoArray<ParamValue>
|
|
22
|
+
normalize?: (value: NeRoArray<ParamValue>) => NeRoArray<ParamValue>
|
|
23
|
+
validate: (value: ParamValue) => boolean
|
|
24
|
+
}
|
|
25
|
+
>
|
|
26
|
+
|
|
27
|
+
type InferParams<S extends ParamsSchema> = {
|
|
28
|
+
[K in keyof S]:
|
|
29
|
+
| (S[K]['required'] extends true
|
|
30
|
+
? never
|
|
31
|
+
: 'default' extends keyof S[K]
|
|
32
|
+
? S[K]['default']
|
|
33
|
+
: undefined)
|
|
34
|
+
| (S[K]['multiple'] extends true
|
|
35
|
+
? NeRoArray<InferParamPredicate<S[K]['validate']>>
|
|
36
|
+
: InferParamPredicate<S[K]['validate']>)
|
|
37
|
+
} & NonNullable<unknown>
|
|
38
|
+
|
|
39
|
+
export class Parser<P extends string, S extends ParamsSchema> {
|
|
40
|
+
public readonly schemaKeys: ReadonlySet<keyof S & string>
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
public readonly prefix: P,
|
|
44
|
+
public readonly schema: S,
|
|
45
|
+
public readonly positionalName?: keyof S & string,
|
|
46
|
+
) {
|
|
47
|
+
this.schemaKeys = new Set(Object.keys(schema))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
format(values: InferParams<S>) {
|
|
51
|
+
const params = new URLSearchParams()
|
|
52
|
+
let positional: string | undefined = undefined
|
|
53
|
+
|
|
54
|
+
for (const key of this.schemaKeys) {
|
|
55
|
+
const value = values[key]
|
|
56
|
+
// Ignore undefined values
|
|
57
|
+
if (value === undefined) continue
|
|
58
|
+
|
|
59
|
+
const schema = this.schema[key]
|
|
60
|
+
|
|
61
|
+
// Normalize the value if a normalization function is provided
|
|
62
|
+
const normalized = schema.normalize
|
|
63
|
+
? schema.normalize(value as any)
|
|
64
|
+
: value
|
|
65
|
+
|
|
66
|
+
// Ignore values that are equal to the default value
|
|
67
|
+
if (!schema.required) {
|
|
68
|
+
if (schema.default === normalized) continue
|
|
69
|
+
if (
|
|
70
|
+
schema.multiple &&
|
|
71
|
+
schema.default &&
|
|
72
|
+
arrayParamEquals(schema.default, normalized as NeRoArray<string>)
|
|
73
|
+
) {
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (Array.isArray(normalized)) {
|
|
79
|
+
if (key === this.positionalName && normalized.length === 1) {
|
|
80
|
+
positional = String(normalized[0]!)
|
|
81
|
+
} else {
|
|
82
|
+
// remove duplicates
|
|
83
|
+
const unique = new Set(normalized.map(String))
|
|
84
|
+
for (const v of unique) params.append(key, v)
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
if (key === this.positionalName) {
|
|
88
|
+
positional = String(normalized)
|
|
89
|
+
} else {
|
|
90
|
+
params.set(key, String(normalized))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return new ScopeStringSyntax(this.prefix, positional, params).toString()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// @NOTE If we needed to ever have more detailed reason as to why parsing
|
|
99
|
+
// fails, this function could easily be updated to return a
|
|
100
|
+
// ValidationResult<T> type that explains the reason for failure.
|
|
101
|
+
parse(syntax: ScopeSyntax<P>) {
|
|
102
|
+
// @NOTE no need to check prefix, since the typing (P generic) already
|
|
103
|
+
// ensures it matches
|
|
104
|
+
|
|
105
|
+
for (const key of syntax.keys()) {
|
|
106
|
+
if (!this.schemaKeys.has(key)) return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const result: Record<
|
|
110
|
+
string,
|
|
111
|
+
undefined | ParamValue | NeRoArray<ParamValue>
|
|
112
|
+
> = Object.create(null)
|
|
113
|
+
|
|
114
|
+
for (const key of this.schemaKeys) {
|
|
115
|
+
const definition = this.schema[key]
|
|
116
|
+
|
|
117
|
+
const param = definition.multiple
|
|
118
|
+
? syntax.getMulti(key)
|
|
119
|
+
: syntax.getSingle(key)
|
|
120
|
+
|
|
121
|
+
if (param === null) {
|
|
122
|
+
return null // Value is not valid
|
|
123
|
+
} else if (param !== undefined) {
|
|
124
|
+
if (key === this.positionalName && syntax.positional !== undefined) {
|
|
125
|
+
// Positional parameter cannot be used with named parameters
|
|
126
|
+
return null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (definition.multiple) {
|
|
130
|
+
// Empty array is not valid
|
|
131
|
+
if (!(param as ParamValue[]).length) return null
|
|
132
|
+
if (!(param as ParamValue[]).every(definition.validate)) {
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
if (!definition.validate(param as ParamValue)) {
|
|
137
|
+
return null
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
result[key] = param as ParamValue | NeRoArray<ParamValue>
|
|
142
|
+
} else if (
|
|
143
|
+
key === this.positionalName &&
|
|
144
|
+
syntax.positional !== undefined
|
|
145
|
+
) {
|
|
146
|
+
// No named parameters found, but there is a positional parameter
|
|
147
|
+
const { positional } = syntax
|
|
148
|
+
if (!definition.validate(positional)) {
|
|
149
|
+
return null
|
|
150
|
+
}
|
|
151
|
+
result[key] = definition.multiple ? [positional] : positional
|
|
152
|
+
} else if (definition.required) {
|
|
153
|
+
return null
|
|
154
|
+
} else {
|
|
155
|
+
result[key] = definition.default
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return result as InferParams<S>
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Two param arrays are considered equal if they contain the same values,
|
|
165
|
+
* regardless of the order and duplicates.
|
|
166
|
+
* @param a - The first array to compare.
|
|
167
|
+
* @param b - The second array to compare.
|
|
168
|
+
*/
|
|
169
|
+
function arrayParamEquals(
|
|
170
|
+
a: readonly unknown[],
|
|
171
|
+
b: readonly unknown[],
|
|
172
|
+
): boolean {
|
|
173
|
+
for (const item of a) if (!b.includes(item)) return false
|
|
174
|
+
for (const item of b) if (!a.includes(item)) return false
|
|
175
|
+
return true
|
|
176
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ScopeStringFor } from './syntax.js'
|
|
2
|
+
import { Matchable } from './util.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Interface destined to provide consistency across parsed permission scopes for
|
|
6
|
+
* resources (blob, repo, etc.).
|
|
7
|
+
*/
|
|
8
|
+
export interface ResourcePermission<R extends string, T> extends Matchable<T> {
|
|
9
|
+
toString(): ScopeStringFor<R>
|
|
10
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { LexPermission } from './lexicon.js'
|
|
2
|
+
import { ScopeSyntax } from './syntax.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Translates a {@link LexPermission} into a {@link ScopeSyntax}.
|
|
6
|
+
*/
|
|
7
|
+
export class LexPermissionSyntax<P extends string = string>
|
|
8
|
+
implements ScopeSyntax<P>
|
|
9
|
+
{
|
|
10
|
+
constructor(
|
|
11
|
+
readonly lexPermission: Readonly<LexPermission & { resource: P }>,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
get prefix() {
|
|
15
|
+
return this.lexPermission.resource
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get positional() {
|
|
19
|
+
return undefined
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get(key: string) {
|
|
23
|
+
// Ignore reserved keywords
|
|
24
|
+
if (key === 'type') return undefined
|
|
25
|
+
if (key === 'resource') return undefined
|
|
26
|
+
|
|
27
|
+
// Ignore inherited properties (toString(), etc.)
|
|
28
|
+
if (!Object.hasOwn(this.lexPermission, key)) return undefined
|
|
29
|
+
|
|
30
|
+
return this.lexPermission[key]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
*keys() {
|
|
34
|
+
for (const key of Object.keys(this.lexPermission)) {
|
|
35
|
+
if (this.get(key) !== undefined) yield key
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getSingle(key: string) {
|
|
40
|
+
const value = this.get(key)
|
|
41
|
+
if (Array.isArray(value)) return null
|
|
42
|
+
return value
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getMulti(key: string) {
|
|
46
|
+
const value = this.get(key)
|
|
47
|
+
if (value === undefined) return undefined
|
|
48
|
+
if (!Array.isArray(value)) return null
|
|
49
|
+
return value
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
toJSON() {
|
|
53
|
+
return this.lexPermission
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { ScopeStringSyntax } from './syntax-string.js'
|
|
2
|
+
import { ScopeStringFor } from './syntax.js'
|
|
3
|
+
|
|
4
|
+
describe('ScopeStringSyntax', () => {
|
|
5
|
+
for (const { scope, content } of [
|
|
6
|
+
{
|
|
7
|
+
scope: 'my-res',
|
|
8
|
+
content: { prefix: 'my-res' },
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
scope: 'my-res:my-pos',
|
|
12
|
+
content: { prefix: 'my-res', positional: 'my-pos' },
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
scope: 'my-res:',
|
|
16
|
+
content: { prefix: 'my-res', positional: '' },
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
scope: 'my-res:foo?x=value&y=value-y',
|
|
20
|
+
content: {
|
|
21
|
+
prefix: 'my-res',
|
|
22
|
+
positional: 'foo',
|
|
23
|
+
params: { x: ['value'], y: ['value-y'] },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
scope: 'my-res?x=value&y=value-y',
|
|
28
|
+
content: { prefix: 'my-res', params: { x: ['value'], y: ['value-y'] } },
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
scope: 'my-res?x=foo&x=bar&x=baz',
|
|
32
|
+
content: { prefix: 'my-res', params: { x: ['foo', 'bar', 'baz'] } },
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
scope: 'rpc:foo.bar?aud=did:foo:bar?lxm=bar.baz',
|
|
36
|
+
content: {
|
|
37
|
+
prefix: 'rpc',
|
|
38
|
+
positional: 'foo.bar',
|
|
39
|
+
params: { aud: ['did:foo:bar?lxm=bar.baz'] },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
] satisfies Array<{
|
|
43
|
+
scope: ScopeStringFor<'my-res' | 'rpc'>
|
|
44
|
+
content: {
|
|
45
|
+
prefix: string
|
|
46
|
+
positional?: string
|
|
47
|
+
params?: Record<string, string[]>
|
|
48
|
+
}
|
|
49
|
+
}>) {
|
|
50
|
+
const syntax = ScopeStringSyntax.fromString<'my-res' | 'rpc'>(scope)
|
|
51
|
+
|
|
52
|
+
describe(scope, () => {
|
|
53
|
+
it('should match the expected syntax', () => {
|
|
54
|
+
expect(syntax).toMatchObject({
|
|
55
|
+
prefix: content.prefix,
|
|
56
|
+
positional: content.positional,
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it(`should match ${scope} prefix`, () => {
|
|
61
|
+
expect(syntax.prefix).toBe(content.prefix)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it(`should return positional parameter`, () => {
|
|
65
|
+
expect(syntax.positional).toBe(content.positional)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it(`should return undefined for nonexistent single-value param`, () => {
|
|
69
|
+
expect(syntax.getSingle('nonexistent')).toBeUndefined()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it(`should return undefined for nonexistent multi-value param`, () => {
|
|
73
|
+
expect(syntax.getMulti('nonexistent')).toBeUndefined()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const { params } = content
|
|
77
|
+
if (params) {
|
|
78
|
+
it(`only contain allowed parameters`, () => {
|
|
79
|
+
const allowedParams = Object.keys(params) as [string, ...string[]]
|
|
80
|
+
expect(
|
|
81
|
+
Array.from(syntax.keys()).every((key) =>
|
|
82
|
+
allowedParams.includes(key),
|
|
83
|
+
),
|
|
84
|
+
).toBe(true)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
for (const [key, values] of Object.entries(params)) {
|
|
88
|
+
it(`should get an array when reading "${key}"`, () => {
|
|
89
|
+
expect(syntax.getMulti(key)).toEqual(values)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
if (values.length === 1) {
|
|
93
|
+
it(`should allow retrieving single-value params`, () => {
|
|
94
|
+
expect(syntax.getSingle(key)).toEqual(values[0])
|
|
95
|
+
})
|
|
96
|
+
} else {
|
|
97
|
+
it(`should return null for multi-value params`, () => {
|
|
98
|
+
expect(syntax.getSingle(key)).toBeNull()
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
describe('invalid positional parameters', () => {
|
|
107
|
+
it('should return null for positional parameters used together with named parameters', () => {
|
|
108
|
+
const syntax = ScopeStringSyntax.fromString('my-res:pos?x=value')
|
|
109
|
+
expect(syntax.getSingle('x')).toBe('value')
|
|
110
|
+
expect(syntax.getMulti('x')).toEqual(['value'])
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
describe('url encoding', () => {
|
|
115
|
+
it('should handle URL encoding in positional parameters', () => {
|
|
116
|
+
const syntax = ScopeStringSyntax.fromString('my-res:my%20pos')
|
|
117
|
+
expect(syntax.positional).toBe('my pos')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('should handle URL encoding in named parameters', () => {
|
|
121
|
+
const syntax = ScopeStringSyntax.fromString('my-res?x=my%20value')
|
|
122
|
+
expect(syntax.getSingle('x')).toBe('my value')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it(`should allow colon (:) in positional parameters`, () => {
|
|
126
|
+
const syntax = ScopeStringSyntax.fromString('my-res:my:pos')
|
|
127
|
+
expect(syntax.positional).toBe('my:pos')
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
})
|