@atproto/oauth-scopes 0.2.2 → 0.3.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 +24 -0
- package/dist/lib/lexicon.d.ts +18 -1
- package/dist/lib/lexicon.d.ts.map +1 -1
- package/dist/lib/lexicon.js.map +1 -1
- package/dist/lib/syntax-lexicon.d.ts +8 -17
- package/dist/lib/syntax-lexicon.d.ts.map +1 -1
- package/dist/lib/syntax-lexicon.js +4 -3
- package/dist/lib/syntax-lexicon.js.map +1 -1
- package/dist/lib/syntax.d.ts +2 -1
- package/dist/lib/syntax.d.ts.map +1 -1
- package/dist/lib/syntax.js +4 -0
- package/dist/lib/syntax.js.map +1 -1
- package/dist/scopes/account-permission.d.ts +5 -5
- package/dist/scopes/account-permission.d.ts.map +1 -1
- package/dist/scopes/account-permission.js +7 -4
- package/dist/scopes/account-permission.js.map +1 -1
- package/dist/scopes/include-scope.d.ts +9 -8
- package/dist/scopes/include-scope.d.ts.map +1 -1
- package/dist/scopes/include-scope.js +46 -29
- package/dist/scopes/include-scope.js.map +1 -1
- package/package.json +3 -4
- package/src/lib/lexicon.ts +21 -1
- package/src/lib/syntax-lexicon.ts +7 -7
- package/src/lib/syntax.ts +8 -1
- package/src/scopes/account-permission.test.ts +9 -9
- package/src/scopes/account-permission.ts +9 -6
- package/src/scopes/include-scope.test.ts +29 -18
- package/src/scopes/include-scope.ts +77 -40
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @atproto/oauth-scopes
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#4383](https://github.com/bluesky-social/atproto/pull/4383) [`8012627`](https://github.com/bluesky-social/atproto/commit/8012627a1226cb2f1c753385ad2497b6b43ffd2e) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Replace `@atproto/lexicon` with `@atproto/lex-document`
|
|
8
|
+
|
|
9
|
+
- [#4353](https://github.com/bluesky-social/atproto/pull/4353) [`0adc852`](https://github.com/bluesky-social/atproto/commit/0adc852c31ffa154c1b93e38182c35880ecdb4ba) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Use arrays to represent "account" permission's `action` attribute, allowing multiple actions to be specified for that resource.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#4382](https://github.com/bluesky-social/atproto/pull/4382) [`be8e6c1`](https://github.com/bluesky-social/atproto/commit/be8e6c1f25814202b98e2616a217599a6c46e0db) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add `toScopes()` utility on `IncludeScope`
|
|
14
|
+
|
|
15
|
+
- [#4382](https://github.com/bluesky-social/atproto/pull/4382) [`be8e6c1`](https://github.com/bluesky-social/atproto/commit/be8e6c1f25814202b98e2616a217599a6c46e0db) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Remove ability to define `blob` permission in permission sets
|
|
16
|
+
|
|
17
|
+
- [#4383](https://github.com/bluesky-social/atproto/pull/4383) [`8012627`](https://github.com/bluesky-social/atproto/commit/8012627a1226cb2f1c753385ad2497b6b43ffd2e) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Replace `@atproto/lexicon-resolver` with `@atproto/lex-resolver`
|
|
18
|
+
|
|
19
|
+
- [#4353](https://github.com/bluesky-social/atproto/pull/4353) [`0adc852`](https://github.com/bluesky-social/atproto/commit/0adc852c31ffa154c1b93e38182c35880ecdb4ba) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Allow lexicon permission data to be readonly
|
|
20
|
+
|
|
21
|
+
- [#4382](https://github.com/bluesky-social/atproto/pull/4382) [`be8e6c1`](https://github.com/bluesky-social/atproto/commit/be8e6c1f25814202b98e2616a217599a6c46e0db) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Disallow `rpc` permissions with specific `aud` in permission-sets
|
|
22
|
+
|
|
23
|
+
- Updated dependencies [[`8012627`](https://github.com/bluesky-social/atproto/commit/8012627a1226cb2f1c753385ad2497b6b43ffd2e), [`bcae2b7`](https://github.com/bluesky-social/atproto/commit/bcae2b77b68da6dc2ec202651c8bf41fd5769f69), [`d396de0`](https://github.com/bluesky-social/atproto/commit/d396de016d1d55d08cfad1dabd3ffd9eaeea76ea)]:
|
|
24
|
+
- @atproto/did@0.2.3
|
|
25
|
+
- @atproto/syntax@0.4.2
|
|
26
|
+
|
|
3
27
|
## 0.2.2
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
package/dist/lib/lexicon.d.ts
CHANGED
|
@@ -1,2 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
import { ParamValue } from './syntax.js';
|
|
2
|
+
export type LexiconPermission<P extends string = string> = {
|
|
3
|
+
readonly type: 'permission';
|
|
4
|
+
readonly resource: P;
|
|
5
|
+
readonly [x: string]: undefined | ParamValue | readonly ParamValue[];
|
|
6
|
+
};
|
|
7
|
+
type LangMap = {
|
|
8
|
+
readonly [Lang in string]?: string;
|
|
9
|
+
};
|
|
10
|
+
export type LexiconPermissionSet = {
|
|
11
|
+
readonly type: 'permission-set';
|
|
12
|
+
readonly permissions: readonly LexiconPermission<string>[];
|
|
13
|
+
readonly title?: string;
|
|
14
|
+
readonly 'title:lang'?: LangMap;
|
|
15
|
+
readonly detail?: string;
|
|
16
|
+
readonly 'detail:lang'?: LangMap;
|
|
17
|
+
};
|
|
18
|
+
export {};
|
|
2
19
|
//# sourceMappingURL=lexicon.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lexicon.d.ts","sourceRoot":"","sources":["../../src/lib/lexicon.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"lexicon.d.ts","sourceRoot":"","sources":["../../src/lib/lexicon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAKxC,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI;IACzD,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAA;IACpB,QAAQ,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,UAAU,EAAE,CAAA;CACrE,CAAA;AAED,KAAK,OAAO,GAAG;IAAE,QAAQ,EAAE,IAAI,IAAI,MAAM,CAAC,CAAC,EAAE,MAAM;CAAE,CAAA;AAErD,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;IAC/B,QAAQ,CAAC,WAAW,EAAE,SAAS,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAA;IAC1D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CACjC,CAAA"}
|
package/dist/lib/lexicon.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lexicon.js","sourceRoot":"","sources":["../../src/lib/lexicon.ts"],"names":[],"mappings":"","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"lexicon.js","sourceRoot":"","sources":["../../src/lib/lexicon.ts"],"names":[],"mappings":"","sourcesContent":["import { ParamValue } from './syntax.js'\n\n// @NOTE Not types from from '@atproto/lex-document' because we want a readonly\n// version here to prevent accidental mutation.\n\nexport type LexiconPermission<P extends string = string> = {\n readonly type: 'permission'\n readonly resource: P\n readonly [x: string]: undefined | ParamValue | readonly ParamValue[]\n}\n\ntype LangMap = { readonly [Lang in string]?: string }\n\nexport type LexiconPermissionSet = {\n readonly type: 'permission-set'\n readonly permissions: readonly LexiconPermission<string>[]\n readonly title?: string\n readonly 'title:lang'?: LangMap\n readonly detail?: string\n readonly 'detail:lang'?: LangMap\n}\n"]}
|
|
@@ -1,26 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LexiconPermission } from './lexicon.js';
|
|
2
2
|
import { ScopeSyntax } from './syntax.js';
|
|
3
3
|
/**
|
|
4
|
-
* Translates a {@link
|
|
4
|
+
* Translates a {@link LexiconPermission} into a {@link ScopeSyntax}.
|
|
5
5
|
*/
|
|
6
6
|
export declare class LexPermissionSyntax<P extends string = string> implements ScopeSyntax<P> {
|
|
7
|
-
readonly lexPermission:
|
|
8
|
-
|
|
9
|
-
}>;
|
|
10
|
-
constructor(lexPermission: Readonly<LexPermission & {
|
|
11
|
-
resource: P;
|
|
12
|
-
}>);
|
|
7
|
+
readonly lexPermission: LexiconPermission<P>;
|
|
8
|
+
constructor(lexPermission: LexiconPermission<P>);
|
|
13
9
|
get prefix(): P;
|
|
14
10
|
get positional(): undefined;
|
|
15
|
-
get(key: string):
|
|
11
|
+
get(key: string): import("./syntax.js").ParamValue | readonly import("./syntax.js").ParamValue[] | undefined;
|
|
16
12
|
keys(): Generator<string, void, unknown>;
|
|
17
|
-
getSingle(key: string):
|
|
18
|
-
getMulti(key: string): (
|
|
19
|
-
toJSON():
|
|
20
|
-
type: "permission";
|
|
21
|
-
resource: string;
|
|
22
|
-
} & Record<string, string | number | boolean | (string | number | boolean)[] | undefined> & {
|
|
23
|
-
resource: P;
|
|
24
|
-
}>;
|
|
13
|
+
getSingle(key: string): import("./syntax.js").ParamValue | null | undefined;
|
|
14
|
+
getMulti(key: string): readonly import("./syntax.js").ParamValue[] | null | undefined;
|
|
15
|
+
toJSON(): LexiconPermission<P>;
|
|
25
16
|
}
|
|
26
17
|
//# sourceMappingURL=syntax-lexicon.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syntax-lexicon.d.ts","sourceRoot":"","sources":["../../src/lib/syntax-lexicon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"syntax-lexicon.d.ts","sourceRoot":"","sources":["../../src/lib/syntax-lexicon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAIzC;;GAEG;AACH,qBAAa,mBAAmB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CACxD,YAAW,WAAW,CAAC,CAAC,CAAC;IAEb,QAAQ,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC,CAAC;gBAAnC,aAAa,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAExD,IAAI,MAAM,MAET;IAED,IAAI,UAAU,cAEb;IAED,GAAG,CAAC,GAAG,EAAE,MAAM;IAWd,IAAI;IAML,SAAS,CAAC,GAAG,EAAE,MAAM;IAMrB,QAAQ,CAAC,GAAG,EAAE,MAAM;IAOpB,MAAM;CAGP"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LexPermissionSyntax = void 0;
|
|
4
|
+
const isArray = Array.isArray;
|
|
4
5
|
/**
|
|
5
|
-
* Translates a {@link
|
|
6
|
+
* Translates a {@link LexiconPermission} into a {@link ScopeSyntax}.
|
|
6
7
|
*/
|
|
7
8
|
class LexPermissionSyntax {
|
|
8
9
|
constructor(lexPermission) {
|
|
@@ -38,7 +39,7 @@ class LexPermissionSyntax {
|
|
|
38
39
|
}
|
|
39
40
|
getSingle(key) {
|
|
40
41
|
const value = this.get(key);
|
|
41
|
-
if (
|
|
42
|
+
if (isArray(value))
|
|
42
43
|
return null;
|
|
43
44
|
return value;
|
|
44
45
|
}
|
|
@@ -46,7 +47,7 @@ class LexPermissionSyntax {
|
|
|
46
47
|
const value = this.get(key);
|
|
47
48
|
if (value === undefined)
|
|
48
49
|
return undefined;
|
|
49
|
-
if (!
|
|
50
|
+
if (!isArray(value))
|
|
50
51
|
return null;
|
|
51
52
|
return value;
|
|
52
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syntax-lexicon.js","sourceRoot":"","sources":["../../src/lib/syntax-lexicon.ts"],"names":[],"mappings":";;;AAGA;;GAEG;AACH,MAAa,mBAAmB;IAG9B,
|
|
1
|
+
{"version":3,"file":"syntax-lexicon.js","sourceRoot":"","sources":["../../src/lib/syntax-lexicon.ts"],"names":[],"mappings":";;;AAGA,MAAM,OAAO,GAAoD,KAAK,CAAC,OAAO,CAAA;AAE9E;;GAEG;AACH,MAAa,mBAAmB;IAG9B,YAAqB,aAAmC;QAA5C;;;;mBAAS,aAAa;WAAsB;IAAG,CAAC;IAE5D,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAA;IACpC,CAAC;IAED,IAAI,UAAU;QACZ,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,GAAG,CAAC,GAAW;QACb,2BAA2B;QAC3B,IAAI,GAAG,KAAK,MAAM;YAAE,OAAO,SAAS,CAAA;QACpC,IAAI,GAAG,KAAK,UAAU;YAAE,OAAO,SAAS,CAAA;QAExC,iDAAiD;QACjD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,GAAG,CAAC;YAAE,OAAO,SAAS,CAAA;QAE7D,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAA;IAChC,CAAC;IAED,CAAC,IAAI;QACH,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;YAClD,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS;gBAAE,MAAM,GAAG,CAAA;QAC5C,CAAC;IACH,CAAC;IAED,SAAS,CAAC,GAAW;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QAC/B,OAAO,KAAK,CAAA;IACd,CAAC;IAED,QAAQ,CAAC,GAAW;QAClB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,SAAS,CAAA;QACzC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QAChC,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,aAAa,CAAA;IAC3B,CAAC;CACF;AA9CD,kDA8CC","sourcesContent":["import { LexiconPermission } from './lexicon.js'\nimport { ScopeSyntax } from './syntax.js'\n\nconst isArray: (value: unknown) => value is readonly unknown[] = Array.isArray\n\n/**\n * Translates a {@link LexiconPermission} into a {@link ScopeSyntax}.\n */\nexport class LexPermissionSyntax<P extends string = string>\n implements ScopeSyntax<P>\n{\n constructor(readonly lexPermission: LexiconPermission<P>) {}\n\n get prefix() {\n return this.lexPermission.resource\n }\n\n get positional() {\n return undefined\n }\n\n get(key: string) {\n // Ignore reserved keywords\n if (key === 'type') return undefined\n if (key === 'resource') return undefined\n\n // Ignore inherited properties (toString(), etc.)\n if (!Object.hasOwn(this.lexPermission, key)) return undefined\n\n return this.lexPermission[key]\n }\n\n *keys() {\n for (const key of Object.keys(this.lexPermission)) {\n if (this.get(key) !== undefined) yield key\n }\n }\n\n getSingle(key: string) {\n const value = this.get(key)\n if (isArray(value)) return null\n return value\n }\n\n getMulti(key: string) {\n const value = this.get(key)\n if (value === undefined) return undefined\n if (!isArray(value)) return null\n return value\n }\n\n toJSON() {\n return this.lexPermission\n }\n}\n"]}
|
package/dist/lib/syntax.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface ScopeSyntax<P extends string> {
|
|
|
18
18
|
readonly positional?: ParamValue;
|
|
19
19
|
keys(): Iterable<string, void, unknown>;
|
|
20
20
|
getSingle(key: string): ParamValue | null | undefined;
|
|
21
|
-
getMulti(key: string): ParamValue[] | null | undefined;
|
|
21
|
+
getMulti(key: string): readonly ParamValue[] | null | undefined;
|
|
22
22
|
}
|
|
23
|
+
export declare function isScopeSyntaxFor<P extends string>(syntax: ScopeSyntax<string>, prefix: P): syntax is ScopeSyntax<P>;
|
|
23
24
|
//# sourceMappingURL=syntax.d.ts.map
|
package/dist/lib/syntax.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syntax.d.ts","sourceRoot":"","sources":["../../src/lib/syntax.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAElD,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;AAEpC;;GAEG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;AAE/C,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,IACvC,CAAC,GACD,GAAG,CAAC,IAAI,MAAM,EAAE,GAChB,GAAG,CAAC,IAAI,MAAM,EAAE,CAAA;AAEpB;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,EAC/C,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,CAAC,GACR,KAAK,IAAI,cAAc,CAAC,CAAC,CAAC,CAc5B;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,MAAM;IAC3C,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IAClB,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAA;IAChC,IAAI,IAAI,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;IACvC,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,GAAG,SAAS,CAAA;IACrD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,GAAG,IAAI,GAAG,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"syntax.d.ts","sourceRoot":"","sources":["../../src/lib/syntax.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAElD,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;AAEpC;;GAEG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;AAE/C,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,IACvC,CAAC,GACD,GAAG,CAAC,IAAI,MAAM,EAAE,GAChB,GAAG,CAAC,IAAI,MAAM,EAAE,CAAA;AAEpB;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,EAC/C,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,CAAC,GACR,KAAK,IAAI,cAAc,CAAC,CAAC,CAAC,CAc5B;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,MAAM;IAC3C,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IAClB,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAA;IAChC,IAAI,IAAI,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;IACvC,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,GAAG,SAAS,CAAA;IACrD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,UAAU,EAAE,GAAG,IAAI,GAAG,SAAS,CAAA;CAChE;AAED,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,EAC/C,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,EAC3B,MAAM,EAAE,CAAC,GACR,MAAM,IAAI,WAAW,CAAC,CAAC,CAAC,CAE1B"}
|
package/dist/lib/syntax.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.isScopeStringFor = isScopeStringFor;
|
|
4
|
+
exports.isScopeSyntaxFor = isScopeSyntaxFor;
|
|
4
5
|
/**
|
|
5
6
|
* Allows to quickly check if a scope is for a specific resource.
|
|
6
7
|
*/
|
|
@@ -19,4 +20,7 @@ function isScopeStringFor(value, prefix) {
|
|
|
19
20
|
return value === prefix;
|
|
20
21
|
}
|
|
21
22
|
}
|
|
23
|
+
function isScopeSyntaxFor(syntax, prefix) {
|
|
24
|
+
return syntax.prefix === prefix;
|
|
25
|
+
}
|
|
22
26
|
//# sourceMappingURL=syntax.js.map
|
package/dist/lib/syntax.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syntax.js","sourceRoot":"","sources":["../../src/lib/syntax.ts"],"names":[],"mappings":";;AAiBA,4CAiBC;
|
|
1
|
+
{"version":3,"file":"syntax.js","sourceRoot":"","sources":["../../src/lib/syntax.ts"],"names":[],"mappings":";;AAiBA,4CAiBC;AAcD,4CAKC;AAvCD;;GAEG;AACH,SAAgB,gBAAgB,CAC9B,KAAa,EACb,MAAS;IAET,IAAI,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QACjC,8CAA8C;QAC9C,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAChD,IAAI,QAAQ,KAAK,IAAI,CAAC,OAAO,IAAI,QAAQ,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;YAC3D,OAAO,KAAK,CAAA;QACd,CAAC;QAED,6BAA6B;QAC7B,OAAO,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;IACjC,CAAC;SAAM,CAAC;QACN,iCAAiC;QACjC,OAAO,KAAK,KAAK,MAAM,CAAA;IACzB,CAAC;AACH,CAAC;AAcD,SAAgB,gBAAgB,CAC9B,MAA2B,EAC3B,MAAS;IAET,OAAO,MAAM,CAAC,MAAM,KAAK,MAAM,CAAA;AACjC,CAAC","sourcesContent":["export type ParamValue = string | number | boolean\n\nexport type NeArray<T> = [T, ...T[]]\n\n/**\n * Non-empty readonly array\n */\nexport type NeRoArray<T> = readonly [T, ...T[]]\n\nexport type ScopeStringFor<P extends string> =\n | P\n | `${P}:${string}`\n | `${P}?${string}`\n\n/**\n * Allows to quickly check if a scope is for a specific resource.\n */\nexport function isScopeStringFor<P extends string>(\n value: string,\n prefix: P,\n): value is ScopeStringFor<P> {\n if (value.length > prefix.length) {\n // First, check the next char is either : or ?\n const nextChar = value.charCodeAt(prefix.length)\n if (nextChar !== 0x3a /* : */ && nextChar !== 0x3f /* ? */) {\n return false\n }\n\n // Then check the full prefix\n return value.startsWith(prefix)\n } else {\n // value and prefix must be equal\n return value === prefix\n }\n}\n\n/**\n * Abstract interface that allows parsing various syntaxes into permission\n * representations.\n */\nexport interface ScopeSyntax<P extends string> {\n readonly prefix: P\n readonly positional?: ParamValue\n keys(): Iterable<string, void, unknown>\n getSingle(key: string): ParamValue | null | undefined\n getMulti(key: string): readonly ParamValue[] | null | undefined\n}\n\nexport function isScopeSyntaxFor<P extends string>(\n syntax: ScopeSyntax<string>,\n prefix: P,\n): syntax is ScopeSyntax<P> {\n return syntax.prefix === prefix\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Parser } from '../lib/parser.js';
|
|
2
2
|
import { ResourcePermission } from '../lib/resource-permission.js';
|
|
3
|
-
import { ScopeSyntax } from '../lib/syntax.js';
|
|
3
|
+
import { NeRoArray, ScopeSyntax } from '../lib/syntax.js';
|
|
4
4
|
export declare const ACCOUNT_ATTRIBUTES: readonly ["email", "repo", "status"];
|
|
5
5
|
export type AccountAttribute = (typeof ACCOUNT_ATTRIBUTES)[number];
|
|
6
6
|
export declare const ACCOUNT_ACTIONS: readonly ["read", "manage"];
|
|
@@ -11,8 +11,8 @@ export type AccountPermissionMatch = {
|
|
|
11
11
|
};
|
|
12
12
|
export declare class AccountPermission implements ResourcePermission<'account', AccountPermissionMatch> {
|
|
13
13
|
readonly attr: AccountAttribute;
|
|
14
|
-
readonly action: AccountAction
|
|
15
|
-
constructor(attr: AccountAttribute, action: AccountAction);
|
|
14
|
+
readonly action: NeRoArray<AccountAction>;
|
|
15
|
+
constructor(attr: AccountAttribute, action: NeRoArray<AccountAction>);
|
|
16
16
|
matches(options: AccountPermissionMatch): boolean;
|
|
17
17
|
toString(): import("../lib/syntax.js").ScopeStringFor<"account">;
|
|
18
18
|
protected static readonly parser: Parser<"account", {
|
|
@@ -22,10 +22,10 @@ export declare class AccountPermission implements ResourcePermission<'account',
|
|
|
22
22
|
validate: (value: unknown) => value is "email" | "repo" | "status";
|
|
23
23
|
};
|
|
24
24
|
action: {
|
|
25
|
-
multiple:
|
|
25
|
+
multiple: true;
|
|
26
26
|
required: false;
|
|
27
27
|
validate: (value: unknown) => value is "read" | "manage";
|
|
28
|
-
default: "read";
|
|
28
|
+
default: ["read"];
|
|
29
29
|
};
|
|
30
30
|
}>;
|
|
31
31
|
static fromString(scope: string): AccountPermission | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account-permission.d.ts","sourceRoot":"","sources":["../../src/scopes/account-permission.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAA;AAElE,OAAO,EAAE,WAAW,EAAoB,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"account-permission.d.ts","sourceRoot":"","sources":["../../src/scopes/account-permission.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAA;AAElE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAoB,MAAM,kBAAkB,CAAA;AAG3E,eAAO,MAAM,kBAAkB,sCAIpB,CAAA;AACX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAA;AAElE,eAAO,MAAM,eAAe,6BAA6C,CAAA;AACzE,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAE5D,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,gBAAgB,CAAA;IACtB,MAAM,EAAE,aAAa,CAAA;CACtB,CAAA;AAED,qBAAa,iBACX,YAAW,kBAAkB,CAAC,SAAS,EAAE,sBAAsB,CAAC;aAG9C,IAAI,EAAE,gBAAgB;aACtB,MAAM,EAAE,SAAS,CAAC,aAAa,CAAC;gBADhC,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,SAAS,CAAC,aAAa,CAAC;IAGlD,OAAO,CAAC,OAAO,EAAE,sBAAsB;IAOvC,QAAQ;IAIR,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM;;;;;;;;;;;;OAgB/B;IAED,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM;IAM/B,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,WAAW,CAAC,SAAS,CAAC;IAOhD,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,sBAAsB;CAMtD"}
|
|
@@ -28,7 +28,7 @@ class AccountPermission {
|
|
|
28
28
|
}
|
|
29
29
|
matches(options) {
|
|
30
30
|
return (this.attr === options.attr &&
|
|
31
|
-
(this.action
|
|
31
|
+
(this.action.includes('manage') || this.action.includes(options.action)));
|
|
32
32
|
}
|
|
33
33
|
toString() {
|
|
34
34
|
return AccountPermission.parser.format(this);
|
|
@@ -46,7 +46,10 @@ class AccountPermission {
|
|
|
46
46
|
return new AccountPermission(result.attr, result.action);
|
|
47
47
|
}
|
|
48
48
|
static scopeNeededFor(options) {
|
|
49
|
-
return AccountPermission.parser.format(
|
|
49
|
+
return AccountPermission.parser.format({
|
|
50
|
+
attr: options.attr,
|
|
51
|
+
action: [options.action],
|
|
52
|
+
});
|
|
50
53
|
}
|
|
51
54
|
}
|
|
52
55
|
exports.AccountPermission = AccountPermission;
|
|
@@ -61,10 +64,10 @@ Object.defineProperty(AccountPermission, "parser", {
|
|
|
61
64
|
validate: (0, util_js_1.knownValuesValidator)(exports.ACCOUNT_ATTRIBUTES),
|
|
62
65
|
},
|
|
63
66
|
action: {
|
|
64
|
-
multiple:
|
|
67
|
+
multiple: true,
|
|
65
68
|
required: false,
|
|
66
69
|
validate: (0, util_js_1.knownValuesValidator)(exports.ACCOUNT_ACTIONS),
|
|
67
|
-
default: 'read',
|
|
70
|
+
default: ['read'],
|
|
68
71
|
},
|
|
69
72
|
}, 'attr')
|
|
70
73
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account-permission.js","sourceRoot":"","sources":["../../src/scopes/account-permission.ts"],"names":[],"mappings":";;;AAAA,gDAAyC;AAEzC,8DAA2D;AAC3D,
|
|
1
|
+
{"version":3,"file":"account-permission.js","sourceRoot":"","sources":["../../src/scopes/account-permission.ts"],"names":[],"mappings":";;;AAAA,gDAAyC;AAEzC,8DAA2D;AAC3D,gDAA2E;AAC3E,4CAAqD;AAExC,QAAA,kBAAkB,GAAG,MAAM,CAAC,MAAM,CAAC;IAC9C,OAAO;IACP,MAAM;IACN,QAAQ;CACA,CAAC,CAAA;AAGE,QAAA,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAU,CAAC,CAAA;AAQzE,MAAa,iBAAiB;IAG5B,YACkB,IAAsB,EACtB,MAAgC;QADhD;;;;mBAAgB,IAAI;WAAkB;QACtC;;;;mBAAgB,MAAM;WAA0B;IAC/C,CAAC;IAEJ,OAAO,CAAC,OAA+B;QACrC,OAAO,CACL,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI;YAC1B,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CACzE,CAAA;IACH,CAAC;IAED,QAAQ;QACN,OAAO,iBAAiB,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC9C,CAAC;IAoBD,MAAM,CAAC,UAAU,CAAC,KAAa;QAC7B,IAAI,CAAC,IAAA,4BAAgB,EAAC,KAAK,EAAE,SAAS,CAAC;YAAE,OAAO,IAAI,CAAA;QACpD,MAAM,MAAM,GAAG,oCAAiB,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;QAClD,OAAO,iBAAiB,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;IAC7C,CAAC;IAED,MAAM,CAAC,UAAU,CAAC,MAA8B;QAC9C,MAAM,MAAM,GAAG,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACrD,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAA;QAExB,OAAO,IAAI,iBAAiB,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IAC1D,CAAC;IAED,MAAM,CAAC,cAAc,CAAC,OAA+B;QACnD,OAAO,iBAAiB,CAAC,MAAM,CAAC,MAAM,CAAC;YACrC,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;SACzB,CAAC,CAAA;IACJ,CAAC;;AAvDH,8CAwDC;AArC2B;;;;WAAS,IAAI,kBAAM,CAC3C,SAAS,EACT;QACE,IAAI,EAAE;YACJ,QAAQ,EAAE,KAAK;YACf,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,IAAA,8BAAoB,EAAC,0BAAkB,CAAC;SACnD;QACD,MAAM,EAAE;YACN,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,KAAK;YACf,QAAQ,EAAE,IAAA,8BAAoB,EAAC,uBAAe,CAAC;YAC/C,OAAO,EAAE,CAAC,MAAe,CAAC;SAC3B;KACF,EACD,MAAM,CACP;EAhB+B,CAgB/B","sourcesContent":["import { Parser } from '../lib/parser.js'\nimport { ResourcePermission } from '../lib/resource-permission.js'\nimport { ScopeStringSyntax } from '../lib/syntax-string.js'\nimport { NeRoArray, ScopeSyntax, isScopeStringFor } from '../lib/syntax.js'\nimport { knownValuesValidator } from '../lib/util.js'\n\nexport const ACCOUNT_ATTRIBUTES = Object.freeze([\n 'email',\n 'repo',\n 'status',\n] as const)\nexport type AccountAttribute = (typeof ACCOUNT_ATTRIBUTES)[number]\n\nexport const ACCOUNT_ACTIONS = Object.freeze(['read', 'manage'] as const)\nexport type AccountAction = (typeof ACCOUNT_ACTIONS)[number]\n\nexport type AccountPermissionMatch = {\n attr: AccountAttribute\n action: AccountAction\n}\n\nexport class AccountPermission\n implements ResourcePermission<'account', AccountPermissionMatch>\n{\n constructor(\n public readonly attr: AccountAttribute,\n public readonly action: NeRoArray<AccountAction>,\n ) {}\n\n matches(options: AccountPermissionMatch) {\n return (\n this.attr === options.attr &&\n (this.action.includes('manage') || this.action.includes(options.action))\n )\n }\n\n toString() {\n return AccountPermission.parser.format(this)\n }\n\n protected static readonly parser = new Parser(\n 'account',\n {\n attr: {\n multiple: false,\n required: true,\n validate: knownValuesValidator(ACCOUNT_ATTRIBUTES),\n },\n action: {\n multiple: true,\n required: false,\n validate: knownValuesValidator(ACCOUNT_ACTIONS),\n default: ['read' as const],\n },\n },\n 'attr',\n )\n\n static fromString(scope: string) {\n if (!isScopeStringFor(scope, 'account')) return null\n const syntax = ScopeStringSyntax.fromString(scope)\n return AccountPermission.fromSyntax(syntax)\n }\n\n static fromSyntax(syntax: ScopeSyntax<'account'>) {\n const result = AccountPermission.parser.parse(syntax)\n if (!result) return null\n\n return new AccountPermission(result.attr, result.action)\n }\n\n static scopeNeededFor(options: AccountPermissionMatch) {\n return AccountPermission.parser.format({\n attr: options.attr,\n action: [options.action],\n })\n }\n}\n"]}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { AtprotoAudience } from '@atproto/did';
|
|
2
|
-
import {
|
|
2
|
+
import { LexiconPermission, LexiconPermissionSet } from '../lib/lexicon.js';
|
|
3
3
|
import { Nsid, isNsid } from '../lib/nsid.js';
|
|
4
4
|
import { Parser } from '../lib/parser.js';
|
|
5
|
-
import { ScopeSyntax } from '../lib/syntax.js';
|
|
6
|
-
import { BlobPermission } from './blob-permission.js';
|
|
5
|
+
import { ScopeStringFor, ScopeSyntax } from '../lib/syntax.js';
|
|
7
6
|
import { RepoPermission } from './repo-permission.js';
|
|
8
7
|
import { RpcPermission } from './rpc-permission.js';
|
|
9
|
-
export { type
|
|
8
|
+
export { type LexiconPermission, type LexiconPermissionSet, type Nsid, isNsid };
|
|
10
9
|
/**
|
|
11
10
|
* This is used to handle "include:" oauth scope values, used to include
|
|
12
11
|
* permissions from a lexicon defined permission set. Not being a resource
|
|
@@ -16,20 +15,22 @@ export declare class IncludeScope {
|
|
|
16
15
|
readonly nsid: Nsid;
|
|
17
16
|
readonly aud: undefined | AtprotoAudience;
|
|
18
17
|
constructor(nsid: Nsid, aud?: undefined | AtprotoAudience);
|
|
19
|
-
toString():
|
|
18
|
+
toString(): ScopeStringFor<"include">;
|
|
19
|
+
toPermissions(permissionSet: LexiconPermissionSet): Array<RepoPermission | RpcPermission>;
|
|
20
|
+
toScopes(permissionSet: LexiconPermissionSet): Array<ScopeStringFor<'repo' | 'rpc'>>;
|
|
20
21
|
/**
|
|
21
22
|
* Converts an "include:" to the list of permissions it includes, based on the
|
|
22
23
|
* lexicon defined permission set.
|
|
23
24
|
*/
|
|
24
|
-
|
|
25
|
-
protected
|
|
25
|
+
buildPermissions(permissionSet: LexiconPermissionSet): Generator<RepoPermission | RpcPermission, void, unknown>;
|
|
26
|
+
protected parseLexPermission(permission: LexiconPermission): ScopeSyntax<'repo' | 'rpc'> | null;
|
|
26
27
|
/**
|
|
27
28
|
* Verifies that a permission included through a lexicon permission set is
|
|
28
29
|
* allowed in the context of the `include:` scope. This basically checks that
|
|
29
30
|
* the permission is "under" the namespace authority of the `include:` scope,
|
|
30
31
|
* and that it only contains "repo:", "rpc:", or "blob:" permissions.
|
|
31
32
|
*/
|
|
32
|
-
protected isAllowedPermission(permission:
|
|
33
|
+
protected isAllowedPermission(permission: RpcPermission | RepoPermission): boolean;
|
|
33
34
|
/**
|
|
34
35
|
* Verifies that a permission item's nsid is under the same authority as the
|
|
35
36
|
* nsid of the lexicon itself (which is the same as the nsid of the `include:`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"include-scope.d.ts","sourceRoot":"","sources":["../../src/scopes/include-scope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAqB,MAAM,cAAc,CAAA;AACjE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"include-scope.d.ts","sourceRoot":"","sources":["../../src/scopes/include-scope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAqB,MAAM,cAAc,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC3E,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAGzC,OAAO,EACL,cAAc,EACd,WAAW,EAGZ,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,oBAAoB,EAAE,KAAK,IAAI,EAAE,MAAM,EAAE,CAAA;AAE/E;;;;GAIG;AACH,qBAAa,YAAY;aAEL,IAAI,EAAE,IAAI;aACV,GAAG,EAAE,SAAS,GAAG,eAAe;gBADhC,IAAI,EAAE,IAAI,EACV,GAAG,GAAE,SAAS,GAAG,eAA2B;IAG9D,QAAQ;IAIR,aAAa,CACX,aAAa,EAAE,oBAAoB,GAClC,KAAK,CAAC,cAAc,GAAG,aAAa,CAAC;IAIxC,QAAQ,CACN,aAAa,EAAE,oBAAoB,GAClC,KAAK,CAAC,cAAc,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;IAIxC;;;OAGG;IACF,gBAAgB,CACf,aAAa,EAAE,oBAAoB,GAClC,SAAS,CAAC,cAAc,GAAG,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC;IAc3D,SAAS,CAAC,kBAAkB,CAC1B,UAAU,EAAE,iBAAiB,GAC5B,WAAW,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,IAAI;IAkCrC;;;;;OAKG;IACH,SAAS,CAAC,mBAAmB,CAC3B,UAAU,EAAE,aAAa,GAAG,cAAc,GACzC,OAAO;IAYV;;;;OAIG;IACI,mBAAmB,CAAC,SAAS,EAAE,GAAG,GAAG,IAAI;IAgChD,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM;;;;;;;;;;;OAe/B;IAED,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM;IAM/B,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,WAAW,CAAC,SAAS,CAAC;CAKjD"}
|
|
@@ -8,7 +8,6 @@ const parser_js_1 = require("../lib/parser.js");
|
|
|
8
8
|
const syntax_lexicon_js_1 = require("../lib/syntax-lexicon.js");
|
|
9
9
|
const syntax_string_js_1 = require("../lib/syntax-string.js");
|
|
10
10
|
const syntax_js_1 = require("../lib/syntax.js");
|
|
11
|
-
const blob_permission_js_1 = require("./blob-permission.js");
|
|
12
11
|
const repo_permission_js_1 = require("./repo-permission.js");
|
|
13
12
|
const rpc_permission_js_1 = require("./rpc-permission.js");
|
|
14
13
|
/**
|
|
@@ -34,30 +33,54 @@ class IncludeScope {
|
|
|
34
33
|
toString() {
|
|
35
34
|
return IncludeScope.parser.format(this);
|
|
36
35
|
}
|
|
36
|
+
toPermissions(permissionSet) {
|
|
37
|
+
return Array.from(this.buildPermissions(permissionSet));
|
|
38
|
+
}
|
|
39
|
+
toScopes(permissionSet) {
|
|
40
|
+
return Array.from(this.buildPermissions(permissionSet), (p) => p.toString());
|
|
41
|
+
}
|
|
37
42
|
/**
|
|
38
43
|
* Converts an "include:" to the list of permissions it includes, based on the
|
|
39
44
|
* lexicon defined permission set.
|
|
40
45
|
*/
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
.
|
|
44
|
-
|
|
46
|
+
*buildPermissions(permissionSet) {
|
|
47
|
+
for (const lexPermission of permissionSet.permissions) {
|
|
48
|
+
const syntax = this.parseLexPermission(lexPermission);
|
|
49
|
+
if (!syntax)
|
|
50
|
+
continue;
|
|
51
|
+
const resourcePermission = toResourcePermission(syntax);
|
|
52
|
+
if (!resourcePermission)
|
|
53
|
+
continue;
|
|
54
|
+
if (this.isAllowedPermission(resourcePermission)) {
|
|
55
|
+
yield resourcePermission;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
45
58
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
parseLexPermission(permission) {
|
|
60
|
+
// This function converts permissions listed in the permission set into
|
|
61
|
+
// their respective ScopeSyntax representations, handling special cases as
|
|
62
|
+
// needed.
|
|
63
|
+
if (isLexPermissionForResource(permission, 'repo')) {
|
|
64
|
+
return new syntax_lexicon_js_1.LexPermissionSyntax(permission);
|
|
65
|
+
}
|
|
66
|
+
if (isLexPermissionForResource(permission, 'rpc')) {
|
|
67
|
+
// "rpc" permissions with a defined audience are not allowed in permission
|
|
68
|
+
// sets
|
|
69
|
+
if (permission.aud !== undefined && permission.aud !== '*') {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
51
72
|
// "rpc" permissions can "inherit" their audience from "aud" param defined
|
|
52
73
|
// in the "include:<nsid>?aud=<audience>" scope the permission set was
|
|
53
74
|
// loaded from.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
if (permission.inheritAud === true &&
|
|
76
|
+
permission.aud === undefined &&
|
|
77
|
+
this.aud !== undefined) {
|
|
78
|
+
const { inheritAud, ...rest } = permission;
|
|
79
|
+
return new syntax_lexicon_js_1.LexPermissionSyntax({ aud: this.aud, ...rest });
|
|
80
|
+
}
|
|
81
|
+
return new syntax_lexicon_js_1.LexPermissionSyntax(permission);
|
|
59
82
|
}
|
|
60
|
-
return
|
|
83
|
+
return null;
|
|
61
84
|
}
|
|
62
85
|
/**
|
|
63
86
|
* Verifies that a permission included through a lexicon permission set is
|
|
@@ -72,10 +95,7 @@ class IncludeScope {
|
|
|
72
95
|
if (permission instanceof repo_permission_js_1.RepoPermission) {
|
|
73
96
|
return permission.collection.every(this.isParentAuthorityOf, this);
|
|
74
97
|
}
|
|
75
|
-
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
return false;
|
|
98
|
+
throw new TypeError(`Unexpected permission ${permission}`);
|
|
79
99
|
}
|
|
80
100
|
/**
|
|
81
101
|
* Verifies that a permission item's nsid is under the same authority as the
|
|
@@ -138,19 +158,16 @@ Object.defineProperty(IncludeScope, "parser", {
|
|
|
138
158
|
},
|
|
139
159
|
}, 'nsid')
|
|
140
160
|
});
|
|
141
|
-
function
|
|
142
|
-
if (
|
|
143
|
-
return repo_permission_js_1.RepoPermission.fromSyntax(
|
|
144
|
-
}
|
|
145
|
-
if (isPermissionForResource(permission, 'rpc')) {
|
|
146
|
-
return rpc_permission_js_1.RpcPermission.fromSyntax(new syntax_lexicon_js_1.LexPermissionSyntax(permission));
|
|
161
|
+
function toResourcePermission(syntax) {
|
|
162
|
+
if ((0, syntax_js_1.isScopeSyntaxFor)(syntax, 'repo')) {
|
|
163
|
+
return repo_permission_js_1.RepoPermission.fromSyntax(syntax);
|
|
147
164
|
}
|
|
148
|
-
if (
|
|
149
|
-
return
|
|
165
|
+
if ((0, syntax_js_1.isScopeSyntaxFor)(syntax, 'rpc')) {
|
|
166
|
+
return rpc_permission_js_1.RpcPermission.fromSyntax(syntax);
|
|
150
167
|
}
|
|
151
168
|
return null;
|
|
152
169
|
}
|
|
153
|
-
function
|
|
170
|
+
function isLexPermissionForResource(permission, type) {
|
|
154
171
|
return permission.resource === type;
|
|
155
172
|
}
|
|
156
173
|
//# sourceMappingURL=include-scope.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"include-scope.js","sourceRoot":"","sources":["../../src/scopes/include-scope.ts"],"names":[],"mappings":"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","sourcesContent":["import { AtprotoAudience, isAtprotoAudience } from '@atproto/did'\nimport { LexPermission, LexPermissionSet } from '../lib/lexicon.js'\nimport { Nsid, isNsid } from '../lib/nsid.js'\nimport { Parser } from '../lib/parser.js'\nimport { LexPermissionSyntax } from '../lib/syntax-lexicon.js'\nimport { ScopeStringSyntax } from '../lib/syntax-string.js'\nimport { ScopeSyntax, isScopeStringFor } from '../lib/syntax.js'\nimport { BlobPermission } from './blob-permission.js'\nimport { RepoPermission } from './repo-permission.js'\nimport { RpcPermission } from './rpc-permission.js'\n\nexport { type LexPermission, type LexPermissionSet, type Nsid, isNsid }\n\n/**\n * This is used to handle \"include:\" oauth scope values, used to include\n * permissions from a lexicon defined permission set. Not being a resource\n * permission, it does not implement `Matchable`.\n */\nexport class IncludeScope {\n constructor(\n public readonly nsid: Nsid,\n public readonly aud: undefined | AtprotoAudience = undefined,\n ) {}\n\n toString() {\n return IncludeScope.parser.format(this)\n }\n\n /**\n * Converts an \"include:\" to the list of permissions it includes, based on the\n * lexicon defined permission set.\n */\n toPermissions(permissionSet: LexPermissionSet) {\n return permissionSet.permissions\n .map(this.parsePermission, this)\n .filter(this.isAllowedPermission, this)\n }\n\n protected parsePermission(permission: LexPermission) {\n if (\n permission.resource === 'rpc' &&\n permission.inheritAud === true &&\n permission.aud === undefined &&\n this.aud !== undefined\n ) {\n // \"rpc\" permissions can \"inherit\" their audience from \"aud\" param defined\n // in the \"include:<nsid>?aud=<audience>\" scope the permission set was\n // loaded from.\n return parsePermission({\n ...permission,\n inheritAud: undefined,\n aud: this.aud,\n })\n }\n\n return parsePermission(permission)\n }\n\n /**\n * Verifies that a permission included through a lexicon permission set is\n * allowed in the context of the `include:` scope. This basically checks that\n * the permission is \"under\" the namespace authority of the `include:` scope,\n * and that it only contains \"repo:\", \"rpc:\", or \"blob:\" permissions.\n */\n protected isAllowedPermission(\n permission: unknown,\n ): permission is RpcPermission | RepoPermission | BlobPermission {\n if (permission instanceof RpcPermission) {\n return permission.lxm.every(this.isParentAuthorityOf, this)\n }\n\n if (permission instanceof RepoPermission) {\n return permission.collection.every(this.isParentAuthorityOf, this)\n }\n\n if (permission instanceof BlobPermission) {\n return true\n }\n\n return false\n }\n\n /**\n * Verifies that a permission item's nsid is under the same authority as the\n * nsid of the lexicon itself (which is the same as the nsid of the `include:`\n * scope).\n */\n public isParentAuthorityOf(otherNsid: '*' | Nsid) {\n if (otherNsid === '*') {\n return false\n }\n\n const lexiconNsid = this.nsid\n\n const groupPrefixEnd = lexiconNsid.lastIndexOf('.')\n\n // There should always be a dot, but since this is a security feature, let's\n // be strict about it.\n if (groupPrefixEnd === -1) {\n throw new TypeError('Dot character (\".\") missing from lexicon NSID')\n }\n\n // Make sure that otherNsid is at least as long as the \"group prefix\"\n if (groupPrefixEnd >= otherNsid.length - 1) {\n return false\n }\n\n // Make sure that the \"otherNsid\" starts with the group of the lexiconNsid,\n // up to the dot itself. We check in reverse order as nsids tend to have\n // long common prefixes.\n for (let i = groupPrefixEnd; i >= 0; i--) {\n if (lexiconNsid.charCodeAt(i) !== otherNsid.charCodeAt(i)) {\n return false\n }\n }\n\n return true\n }\n\n protected static readonly parser = new Parser(\n 'include',\n {\n nsid: {\n multiple: false,\n required: true,\n validate: isNsid,\n },\n aud: {\n multiple: false,\n required: false,\n validate: isAtprotoAudience,\n },\n },\n 'nsid',\n )\n\n static fromString(scope: string) {\n if (!isScopeStringFor(scope, 'include')) return null\n const syntax = ScopeStringSyntax.fromString(scope)\n return IncludeScope.fromSyntax(syntax)\n }\n\n static fromSyntax(syntax: ScopeSyntax<'include'>) {\n const result = IncludeScope.parser.parse(syntax)\n if (!result) return null\n return new IncludeScope(result.nsid, result.aud)\n }\n}\n\nfunction parsePermission(permission: LexPermission) {\n if (isPermissionForResource(permission, 'repo')) {\n return RepoPermission.fromSyntax(new LexPermissionSyntax(permission))\n }\n if (isPermissionForResource(permission, 'rpc')) {\n return RpcPermission.fromSyntax(new LexPermissionSyntax(permission))\n }\n if (isPermissionForResource(permission, 'blob')) {\n return BlobPermission.fromSyntax(new LexPermissionSyntax(permission))\n }\n return null\n}\n\nfunction isPermissionForResource<P extends LexPermission, T extends string>(\n permission: P,\n type: T,\n): permission is P & { resource: T } {\n return permission.resource === type\n}\n"]}
|
|
1
|
+
{"version":3,"file":"include-scope.js","sourceRoot":"","sources":["../../src/scopes/include-scope.ts"],"names":[],"mappings":"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","sourcesContent":["import { AtprotoAudience, isAtprotoAudience } from '@atproto/did'\nimport { LexiconPermission, LexiconPermissionSet } from '../lib/lexicon.js'\nimport { Nsid, isNsid } from '../lib/nsid.js'\nimport { Parser } from '../lib/parser.js'\nimport { LexPermissionSyntax } from '../lib/syntax-lexicon.js'\nimport { ScopeStringSyntax } from '../lib/syntax-string.js'\nimport {\n ScopeStringFor,\n ScopeSyntax,\n isScopeStringFor,\n isScopeSyntaxFor,\n} from '../lib/syntax.js'\nimport { RepoPermission } from './repo-permission.js'\nimport { RpcPermission } from './rpc-permission.js'\n\nexport { type LexiconPermission, type LexiconPermissionSet, type Nsid, isNsid }\n\n/**\n * This is used to handle \"include:\" oauth scope values, used to include\n * permissions from a lexicon defined permission set. Not being a resource\n * permission, it does not implement `Matchable`.\n */\nexport class IncludeScope {\n constructor(\n public readonly nsid: Nsid,\n public readonly aud: undefined | AtprotoAudience = undefined,\n ) {}\n\n toString() {\n return IncludeScope.parser.format(this)\n }\n\n toPermissions(\n permissionSet: LexiconPermissionSet,\n ): Array<RepoPermission | RpcPermission> {\n return Array.from(this.buildPermissions(permissionSet))\n }\n\n toScopes(\n permissionSet: LexiconPermissionSet,\n ): Array<ScopeStringFor<'repo' | 'rpc'>> {\n return Array.from(this.buildPermissions(permissionSet), (p) => p.toString())\n }\n\n /**\n * Converts an \"include:\" to the list of permissions it includes, based on the\n * lexicon defined permission set.\n */\n *buildPermissions(\n permissionSet: LexiconPermissionSet,\n ): Generator<RepoPermission | RpcPermission, void, unknown> {\n for (const lexPermission of permissionSet.permissions) {\n const syntax = this.parseLexPermission(lexPermission)\n if (!syntax) continue\n\n const resourcePermission = toResourcePermission(syntax)\n if (!resourcePermission) continue\n\n if (this.isAllowedPermission(resourcePermission)) {\n yield resourcePermission\n }\n }\n }\n\n protected parseLexPermission(\n permission: LexiconPermission,\n ): ScopeSyntax<'repo' | 'rpc'> | null {\n // This function converts permissions listed in the permission set into\n // their respective ScopeSyntax representations, handling special cases as\n // needed.\n\n if (isLexPermissionForResource(permission, 'repo')) {\n return new LexPermissionSyntax(permission)\n }\n\n if (isLexPermissionForResource(permission, 'rpc')) {\n // \"rpc\" permissions with a defined audience are not allowed in permission\n // sets\n if (permission.aud !== undefined && permission.aud !== '*') {\n return null\n }\n\n // \"rpc\" permissions can \"inherit\" their audience from \"aud\" param defined\n // in the \"include:<nsid>?aud=<audience>\" scope the permission set was\n // loaded from.\n if (\n permission.inheritAud === true &&\n permission.aud === undefined &&\n this.aud !== undefined\n ) {\n const { inheritAud, ...rest } = permission\n return new LexPermissionSyntax({ aud: this.aud, ...rest })\n }\n\n return new LexPermissionSyntax(permission)\n }\n\n return null\n }\n\n /**\n * Verifies that a permission included through a lexicon permission set is\n * allowed in the context of the `include:` scope. This basically checks that\n * the permission is \"under\" the namespace authority of the `include:` scope,\n * and that it only contains \"repo:\", \"rpc:\", or \"blob:\" permissions.\n */\n protected isAllowedPermission(\n permission: RpcPermission | RepoPermission,\n ): boolean {\n if (permission instanceof RpcPermission) {\n return permission.lxm.every(this.isParentAuthorityOf, this)\n }\n\n if (permission instanceof RepoPermission) {\n return permission.collection.every(this.isParentAuthorityOf, this)\n }\n\n throw new TypeError(`Unexpected permission ${permission}`)\n }\n\n /**\n * Verifies that a permission item's nsid is under the same authority as the\n * nsid of the lexicon itself (which is the same as the nsid of the `include:`\n * scope).\n */\n public isParentAuthorityOf(otherNsid: '*' | Nsid) {\n if (otherNsid === '*') {\n return false\n }\n\n const lexiconNsid = this.nsid\n\n const groupPrefixEnd = lexiconNsid.lastIndexOf('.')\n\n // There should always be a dot, but since this is a security feature, let's\n // be strict about it.\n if (groupPrefixEnd === -1) {\n throw new TypeError('Dot character (\".\") missing from lexicon NSID')\n }\n\n // Make sure that otherNsid is at least as long as the \"group prefix\"\n if (groupPrefixEnd >= otherNsid.length - 1) {\n return false\n }\n\n // Make sure that the \"otherNsid\" starts with the group of the lexiconNsid,\n // up to the dot itself. We check in reverse order as nsids tend to have\n // long common prefixes.\n for (let i = groupPrefixEnd; i >= 0; i--) {\n if (lexiconNsid.charCodeAt(i) !== otherNsid.charCodeAt(i)) {\n return false\n }\n }\n\n return true\n }\n\n protected static readonly parser = new Parser(\n 'include',\n {\n nsid: {\n multiple: false,\n required: true,\n validate: isNsid,\n },\n aud: {\n multiple: false,\n required: false,\n validate: isAtprotoAudience,\n },\n },\n 'nsid',\n )\n\n static fromString(scope: string) {\n if (!isScopeStringFor(scope, 'include')) return null\n const syntax = ScopeStringSyntax.fromString(scope)\n return IncludeScope.fromSyntax(syntax)\n }\n\n static fromSyntax(syntax: ScopeSyntax<'include'>) {\n const result = IncludeScope.parser.parse(syntax)\n if (!result) return null\n return new IncludeScope(result.nsid, result.aud)\n }\n}\n\nfunction toResourcePermission(\n syntax: ScopeSyntax<'repo' | 'rpc'>,\n): RepoPermission | RpcPermission | null {\n if (isScopeSyntaxFor(syntax, 'repo')) {\n return RepoPermission.fromSyntax(syntax)\n }\n if (isScopeSyntaxFor(syntax, 'rpc')) {\n return RpcPermission.fromSyntax(syntax)\n }\n return null\n}\n\nfunction isLexPermissionForResource<\n P extends { resource: unknown },\n T extends string,\n>(permission: P, type: T): permission is P & { resource: T } {\n return permission.resource === type\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/oauth-scopes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "A library for manipulating and validating ATproto OAuth scopes in TypeScript.",
|
|
6
6
|
"keywords": [
|
|
@@ -25,9 +25,8 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@atproto/did": "^0.2.
|
|
29
|
-
"@atproto/
|
|
30
|
-
"@atproto/syntax": "^0.4.1"
|
|
28
|
+
"@atproto/did": "^0.2.3",
|
|
29
|
+
"@atproto/syntax": "^0.4.2"
|
|
31
30
|
},
|
|
32
31
|
"devDependencies": {
|
|
33
32
|
"jest": "^28.1.2",
|
package/src/lib/lexicon.ts
CHANGED
|
@@ -1 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
import { ParamValue } from './syntax.js'
|
|
2
|
+
|
|
3
|
+
// @NOTE Not types from from '@atproto/lex-document' because we want a readonly
|
|
4
|
+
// version here to prevent accidental mutation.
|
|
5
|
+
|
|
6
|
+
export type LexiconPermission<P extends string = string> = {
|
|
7
|
+
readonly type: 'permission'
|
|
8
|
+
readonly resource: P
|
|
9
|
+
readonly [x: string]: undefined | ParamValue | readonly ParamValue[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type LangMap = { readonly [Lang in string]?: string }
|
|
13
|
+
|
|
14
|
+
export type LexiconPermissionSet = {
|
|
15
|
+
readonly type: 'permission-set'
|
|
16
|
+
readonly permissions: readonly LexiconPermission<string>[]
|
|
17
|
+
readonly title?: string
|
|
18
|
+
readonly 'title:lang'?: LangMap
|
|
19
|
+
readonly detail?: string
|
|
20
|
+
readonly 'detail:lang'?: LangMap
|
|
21
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LexiconPermission } from './lexicon.js'
|
|
2
2
|
import { ScopeSyntax } from './syntax.js'
|
|
3
3
|
|
|
4
|
+
const isArray: (value: unknown) => value is readonly unknown[] = Array.isArray
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
|
-
* Translates a {@link
|
|
7
|
+
* Translates a {@link LexiconPermission} into a {@link ScopeSyntax}.
|
|
6
8
|
*/
|
|
7
9
|
export class LexPermissionSyntax<P extends string = string>
|
|
8
10
|
implements ScopeSyntax<P>
|
|
9
11
|
{
|
|
10
|
-
constructor(
|
|
11
|
-
readonly lexPermission: Readonly<LexPermission & { resource: P }>,
|
|
12
|
-
) {}
|
|
12
|
+
constructor(readonly lexPermission: LexiconPermission<P>) {}
|
|
13
13
|
|
|
14
14
|
get prefix() {
|
|
15
15
|
return this.lexPermission.resource
|
|
@@ -38,14 +38,14 @@ export class LexPermissionSyntax<P extends string = string>
|
|
|
38
38
|
|
|
39
39
|
getSingle(key: string) {
|
|
40
40
|
const value = this.get(key)
|
|
41
|
-
if (
|
|
41
|
+
if (isArray(value)) return null
|
|
42
42
|
return value
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
getMulti(key: string) {
|
|
46
46
|
const value = this.get(key)
|
|
47
47
|
if (value === undefined) return undefined
|
|
48
|
-
if (!
|
|
48
|
+
if (!isArray(value)) return null
|
|
49
49
|
return value
|
|
50
50
|
}
|
|
51
51
|
|
package/src/lib/syntax.ts
CHANGED
|
@@ -43,5 +43,12 @@ export interface ScopeSyntax<P extends string> {
|
|
|
43
43
|
readonly positional?: ParamValue
|
|
44
44
|
keys(): Iterable<string, void, unknown>
|
|
45
45
|
getSingle(key: string): ParamValue | null | undefined
|
|
46
|
-
getMulti(key: string): ParamValue[] | null | undefined
|
|
46
|
+
getMulti(key: string): readonly ParamValue[] | null | undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isScopeSyntaxFor<P extends string>(
|
|
50
|
+
syntax: ScopeSyntax<string>,
|
|
51
|
+
prefix: P,
|
|
52
|
+
): syntax is ScopeSyntax<P> {
|
|
53
|
+
return syntax.prefix === prefix
|
|
47
54
|
}
|
|
@@ -7,21 +7,21 @@ describe('AccountPermission', () => {
|
|
|
7
7
|
const scope1 = AccountPermission.fromString('account:email?action=read')
|
|
8
8
|
expect(scope1).not.toBeNull()
|
|
9
9
|
expect(scope1!.attr).toBe('email')
|
|
10
|
-
expect(scope1!.action).
|
|
10
|
+
expect(scope1!.action).toEqual(['read'])
|
|
11
11
|
|
|
12
12
|
const scope2 = AccountPermission.fromString(
|
|
13
13
|
'account:repo?action=manage',
|
|
14
14
|
)
|
|
15
15
|
expect(scope2).not.toBeNull()
|
|
16
16
|
expect(scope2!.attr).toBe('repo')
|
|
17
|
-
expect(scope2!.action).
|
|
17
|
+
expect(scope2!.action).toEqual(['manage'])
|
|
18
18
|
})
|
|
19
19
|
|
|
20
20
|
it('should parse scope without action (defaults to read)', () => {
|
|
21
21
|
const scope = AccountPermission.fromString('account:status')
|
|
22
22
|
expect(scope).not.toBeNull()
|
|
23
23
|
expect(scope!.attr).toBe('status')
|
|
24
|
-
expect(scope!.action).
|
|
24
|
+
expect(scope!.action).toEqual(['read'])
|
|
25
25
|
})
|
|
26
26
|
|
|
27
27
|
it('should reject invalid attribute names', () => {
|
|
@@ -144,26 +144,26 @@ describe('AccountPermission', () => {
|
|
|
144
144
|
|
|
145
145
|
describe('toString', () => {
|
|
146
146
|
it('should format scope with explicit action', () => {
|
|
147
|
-
const scope = new AccountPermission('email', 'manage')
|
|
147
|
+
const scope = new AccountPermission('email', ['manage'])
|
|
148
148
|
expect(scope.toString()).toBe('account:email?action=manage')
|
|
149
149
|
})
|
|
150
150
|
|
|
151
151
|
it('should format scope with default action', () => {
|
|
152
|
-
const scope = new AccountPermission('repo', 'read')
|
|
152
|
+
const scope = new AccountPermission('repo', ['read'])
|
|
153
153
|
expect(scope.toString()).toBe('account:repo')
|
|
154
154
|
})
|
|
155
155
|
|
|
156
156
|
it('should format all attributes correctly', () => {
|
|
157
|
-
expect(new AccountPermission('email', 'read').toString()).toBe(
|
|
157
|
+
expect(new AccountPermission('email', ['read']).toString()).toBe(
|
|
158
158
|
'account:email',
|
|
159
159
|
)
|
|
160
|
-
expect(new AccountPermission('repo', 'read').toString()).toBe(
|
|
160
|
+
expect(new AccountPermission('repo', ['read']).toString()).toBe(
|
|
161
161
|
'account:repo',
|
|
162
162
|
)
|
|
163
|
-
expect(new AccountPermission('status', 'read').toString()).toBe(
|
|
163
|
+
expect(new AccountPermission('status', ['read']).toString()).toBe(
|
|
164
164
|
'account:status',
|
|
165
165
|
)
|
|
166
|
-
expect(new AccountPermission('email', 'manage').toString()).toBe(
|
|
166
|
+
expect(new AccountPermission('email', ['manage']).toString()).toBe(
|
|
167
167
|
'account:email?action=manage',
|
|
168
168
|
)
|
|
169
169
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Parser } from '../lib/parser.js'
|
|
2
2
|
import { ResourcePermission } from '../lib/resource-permission.js'
|
|
3
3
|
import { ScopeStringSyntax } from '../lib/syntax-string.js'
|
|
4
|
-
import { ScopeSyntax, isScopeStringFor } from '../lib/syntax.js'
|
|
4
|
+
import { NeRoArray, ScopeSyntax, isScopeStringFor } from '../lib/syntax.js'
|
|
5
5
|
import { knownValuesValidator } from '../lib/util.js'
|
|
6
6
|
|
|
7
7
|
export const ACCOUNT_ATTRIBUTES = Object.freeze([
|
|
@@ -24,13 +24,13 @@ export class AccountPermission
|
|
|
24
24
|
{
|
|
25
25
|
constructor(
|
|
26
26
|
public readonly attr: AccountAttribute,
|
|
27
|
-
public readonly action: AccountAction
|
|
27
|
+
public readonly action: NeRoArray<AccountAction>,
|
|
28
28
|
) {}
|
|
29
29
|
|
|
30
30
|
matches(options: AccountPermissionMatch) {
|
|
31
31
|
return (
|
|
32
32
|
this.attr === options.attr &&
|
|
33
|
-
(this.action
|
|
33
|
+
(this.action.includes('manage') || this.action.includes(options.action))
|
|
34
34
|
)
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -47,10 +47,10 @@ export class AccountPermission
|
|
|
47
47
|
validate: knownValuesValidator(ACCOUNT_ATTRIBUTES),
|
|
48
48
|
},
|
|
49
49
|
action: {
|
|
50
|
-
multiple:
|
|
50
|
+
multiple: true,
|
|
51
51
|
required: false,
|
|
52
52
|
validate: knownValuesValidator(ACCOUNT_ACTIONS),
|
|
53
|
-
default: 'read' as const,
|
|
53
|
+
default: ['read' as const],
|
|
54
54
|
},
|
|
55
55
|
},
|
|
56
56
|
'attr',
|
|
@@ -70,6 +70,9 @@ export class AccountPermission
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
static scopeNeededFor(options: AccountPermissionMatch) {
|
|
73
|
-
return AccountPermission.parser.format(
|
|
73
|
+
return AccountPermission.parser.format({
|
|
74
|
+
attr: options.attr,
|
|
75
|
+
action: [options.action],
|
|
76
|
+
})
|
|
74
77
|
}
|
|
75
78
|
}
|
|
@@ -2,7 +2,7 @@ import { ScopeStringFor } from '../lib/syntax'
|
|
|
2
2
|
import { LexPermissionSyntax } from '../lib/syntax-lexicon'
|
|
3
3
|
import { AccountPermission } from './account-permission'
|
|
4
4
|
import { IdentityPermission } from './identity-permission'
|
|
5
|
-
import { IncludeScope,
|
|
5
|
+
import { IncludeScope, LexiconPermissionSet } from './include-scope'
|
|
6
6
|
|
|
7
7
|
describe('IncludeScope', () => {
|
|
8
8
|
describe('static', () => {
|
|
@@ -157,19 +157,18 @@ describe('IncludeScope', () => {
|
|
|
157
157
|
})
|
|
158
158
|
})
|
|
159
159
|
|
|
160
|
-
describe('
|
|
160
|
+
describe('buildPermissions', () => {
|
|
161
161
|
/**
|
|
162
|
-
* Utility that transforms
|
|
162
|
+
* Utility that transforms a (valid) "include:<nsid>" scope and matching
|
|
163
163
|
* (resolved) permission set into the list of permission scopes.
|
|
164
164
|
*/
|
|
165
165
|
const compilePermissions = (
|
|
166
166
|
scope: ScopeStringFor<'include'>,
|
|
167
|
-
permissionSet:
|
|
168
|
-
) =>
|
|
169
|
-
IncludeScope.fromString(scope)?.toPermissions(permissionSet).map(String)
|
|
167
|
+
permissionSet: LexiconPermissionSet,
|
|
168
|
+
) => IncludeScope.fromString(scope)!.toScopes(permissionSet)
|
|
170
169
|
|
|
171
170
|
describe('blob', () => {
|
|
172
|
-
describe('
|
|
171
|
+
describe('rejects', () => {
|
|
173
172
|
it('valid permissions', () => {
|
|
174
173
|
expect(
|
|
175
174
|
compilePermissions('include:com.example.calendar.auth', {
|
|
@@ -182,11 +181,9 @@ describe('IncludeScope', () => {
|
|
|
182
181
|
},
|
|
183
182
|
],
|
|
184
183
|
}),
|
|
185
|
-
).toEqual([
|
|
184
|
+
).toEqual([])
|
|
186
185
|
})
|
|
187
|
-
})
|
|
188
186
|
|
|
189
|
-
describe('rejects', () => {
|
|
190
187
|
it('invalid permissions', () => {
|
|
191
188
|
expect(
|
|
192
189
|
compilePermissions('include:com.example.calendar.auth', {
|
|
@@ -220,7 +217,7 @@ describe('IncludeScope', () => {
|
|
|
220
217
|
|
|
221
218
|
describe('rpc', () => {
|
|
222
219
|
describe('enables', () => {
|
|
223
|
-
it('
|
|
220
|
+
it('allows * aud', () => {
|
|
224
221
|
expect(
|
|
225
222
|
compilePermissions('include:com.example.calendar.auth', {
|
|
226
223
|
type: 'permission-set',
|
|
@@ -228,17 +225,15 @@ describe('IncludeScope', () => {
|
|
|
228
225
|
{
|
|
229
226
|
type: 'permission',
|
|
230
227
|
resource: 'rpc',
|
|
231
|
-
aud: '
|
|
228
|
+
aud: '*',
|
|
232
229
|
lxm: ['com.example.calendar.listEvents'],
|
|
233
230
|
},
|
|
234
231
|
],
|
|
235
232
|
}),
|
|
236
|
-
).toEqual([
|
|
237
|
-
'rpc:com.example.calendar.listEvents?aud=did:web:example.com%23foo',
|
|
238
|
-
])
|
|
233
|
+
).toEqual(['rpc:com.example.calendar.listEvents?aud=*'])
|
|
239
234
|
})
|
|
240
235
|
|
|
241
|
-
it('
|
|
236
|
+
it('inherits aud', () => {
|
|
242
237
|
expect(
|
|
243
238
|
compilePermissions(
|
|
244
239
|
'include:com.example.calendar.auth?aud=did:web:example.com#foo',
|
|
@@ -268,6 +263,22 @@ describe('IncludeScope', () => {
|
|
|
268
263
|
})
|
|
269
264
|
|
|
270
265
|
describe('rejects', () => {
|
|
266
|
+
it('forbids use of specific "aud"', () => {
|
|
267
|
+
expect(
|
|
268
|
+
compilePermissions('include:com.example.calendar.auth', {
|
|
269
|
+
type: 'permission-set',
|
|
270
|
+
permissions: [
|
|
271
|
+
{
|
|
272
|
+
type: 'permission',
|
|
273
|
+
resource: 'rpc',
|
|
274
|
+
aud: 'did:web:example.com#foo',
|
|
275
|
+
lxm: ['com.example.calendar.listEvents'],
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
}),
|
|
279
|
+
).toEqual([])
|
|
280
|
+
})
|
|
281
|
+
|
|
271
282
|
it('invalid "lxm" syntax', () => {
|
|
272
283
|
expect(
|
|
273
284
|
compilePermissions('include:com.example.calendar.auth', {
|
|
@@ -569,7 +580,7 @@ describe('IncludeScope', () => {
|
|
|
569
580
|
type: 'permission',
|
|
570
581
|
resource: 'account',
|
|
571
582
|
attr: 'email',
|
|
572
|
-
action: 'read',
|
|
583
|
+
action: ['read'],
|
|
573
584
|
} as const
|
|
574
585
|
|
|
575
586
|
it('parses valid permission syntax', () => {
|
|
@@ -578,7 +589,7 @@ describe('IncludeScope', () => {
|
|
|
578
589
|
expect(AccountPermission.fromSyntax(syntax)).toMatchObject({
|
|
579
590
|
constructor: AccountPermission,
|
|
580
591
|
attr: 'email',
|
|
581
|
-
action: 'read',
|
|
592
|
+
action: ['read'],
|
|
582
593
|
})
|
|
583
594
|
})
|
|
584
595
|
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { AtprotoAudience, isAtprotoAudience } from '@atproto/did'
|
|
2
|
-
import {
|
|
2
|
+
import { LexiconPermission, LexiconPermissionSet } from '../lib/lexicon.js'
|
|
3
3
|
import { Nsid, isNsid } from '../lib/nsid.js'
|
|
4
4
|
import { Parser } from '../lib/parser.js'
|
|
5
5
|
import { LexPermissionSyntax } from '../lib/syntax-lexicon.js'
|
|
6
6
|
import { ScopeStringSyntax } from '../lib/syntax-string.js'
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
ScopeStringFor,
|
|
9
|
+
ScopeSyntax,
|
|
10
|
+
isScopeStringFor,
|
|
11
|
+
isScopeSyntaxFor,
|
|
12
|
+
} from '../lib/syntax.js'
|
|
9
13
|
import { RepoPermission } from './repo-permission.js'
|
|
10
14
|
import { RpcPermission } from './rpc-permission.js'
|
|
11
15
|
|
|
12
|
-
export { type
|
|
16
|
+
export { type LexiconPermission, type LexiconPermissionSet, type Nsid, isNsid }
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
19
|
* This is used to handle "include:" oauth scope values, used to include
|
|
@@ -26,34 +30,72 @@ export class IncludeScope {
|
|
|
26
30
|
return IncludeScope.parser.format(this)
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
toPermissions(
|
|
34
|
+
permissionSet: LexiconPermissionSet,
|
|
35
|
+
): Array<RepoPermission | RpcPermission> {
|
|
36
|
+
return Array.from(this.buildPermissions(permissionSet))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
toScopes(
|
|
40
|
+
permissionSet: LexiconPermissionSet,
|
|
41
|
+
): Array<ScopeStringFor<'repo' | 'rpc'>> {
|
|
42
|
+
return Array.from(this.buildPermissions(permissionSet), (p) => p.toString())
|
|
43
|
+
}
|
|
44
|
+
|
|
29
45
|
/**
|
|
30
46
|
* Converts an "include:" to the list of permissions it includes, based on the
|
|
31
47
|
* lexicon defined permission set.
|
|
32
48
|
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
*buildPermissions(
|
|
50
|
+
permissionSet: LexiconPermissionSet,
|
|
51
|
+
): Generator<RepoPermission | RpcPermission, void, unknown> {
|
|
52
|
+
for (const lexPermission of permissionSet.permissions) {
|
|
53
|
+
const syntax = this.parseLexPermission(lexPermission)
|
|
54
|
+
if (!syntax) continue
|
|
55
|
+
|
|
56
|
+
const resourcePermission = toResourcePermission(syntax)
|
|
57
|
+
if (!resourcePermission) continue
|
|
58
|
+
|
|
59
|
+
if (this.isAllowedPermission(resourcePermission)) {
|
|
60
|
+
yield resourcePermission
|
|
61
|
+
}
|
|
62
|
+
}
|
|
37
63
|
}
|
|
38
64
|
|
|
39
|
-
protected
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
65
|
+
protected parseLexPermission(
|
|
66
|
+
permission: LexiconPermission,
|
|
67
|
+
): ScopeSyntax<'repo' | 'rpc'> | null {
|
|
68
|
+
// This function converts permissions listed in the permission set into
|
|
69
|
+
// their respective ScopeSyntax representations, handling special cases as
|
|
70
|
+
// needed.
|
|
71
|
+
|
|
72
|
+
if (isLexPermissionForResource(permission, 'repo')) {
|
|
73
|
+
return new LexPermissionSyntax(permission)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (isLexPermissionForResource(permission, 'rpc')) {
|
|
77
|
+
// "rpc" permissions with a defined audience are not allowed in permission
|
|
78
|
+
// sets
|
|
79
|
+
if (permission.aud !== undefined && permission.aud !== '*') {
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
|
|
46
83
|
// "rpc" permissions can "inherit" their audience from "aud" param defined
|
|
47
84
|
// in the "include:<nsid>?aud=<audience>" scope the permission set was
|
|
48
85
|
// loaded from.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
86
|
+
if (
|
|
87
|
+
permission.inheritAud === true &&
|
|
88
|
+
permission.aud === undefined &&
|
|
89
|
+
this.aud !== undefined
|
|
90
|
+
) {
|
|
91
|
+
const { inheritAud, ...rest } = permission
|
|
92
|
+
return new LexPermissionSyntax({ aud: this.aud, ...rest })
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return new LexPermissionSyntax(permission)
|
|
54
96
|
}
|
|
55
97
|
|
|
56
|
-
return
|
|
98
|
+
return null
|
|
57
99
|
}
|
|
58
100
|
|
|
59
101
|
/**
|
|
@@ -63,8 +105,8 @@ export class IncludeScope {
|
|
|
63
105
|
* and that it only contains "repo:", "rpc:", or "blob:" permissions.
|
|
64
106
|
*/
|
|
65
107
|
protected isAllowedPermission(
|
|
66
|
-
permission:
|
|
67
|
-
):
|
|
108
|
+
permission: RpcPermission | RepoPermission,
|
|
109
|
+
): boolean {
|
|
68
110
|
if (permission instanceof RpcPermission) {
|
|
69
111
|
return permission.lxm.every(this.isParentAuthorityOf, this)
|
|
70
112
|
}
|
|
@@ -73,11 +115,7 @@ export class IncludeScope {
|
|
|
73
115
|
return permission.collection.every(this.isParentAuthorityOf, this)
|
|
74
116
|
}
|
|
75
117
|
|
|
76
|
-
|
|
77
|
-
return true
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return false
|
|
118
|
+
throw new TypeError(`Unexpected permission ${permission}`)
|
|
81
119
|
}
|
|
82
120
|
|
|
83
121
|
/**
|
|
@@ -147,22 +185,21 @@ export class IncludeScope {
|
|
|
147
185
|
}
|
|
148
186
|
}
|
|
149
187
|
|
|
150
|
-
function
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return RpcPermission.fromSyntax(new LexPermissionSyntax(permission))
|
|
188
|
+
function toResourcePermission(
|
|
189
|
+
syntax: ScopeSyntax<'repo' | 'rpc'>,
|
|
190
|
+
): RepoPermission | RpcPermission | null {
|
|
191
|
+
if (isScopeSyntaxFor(syntax, 'repo')) {
|
|
192
|
+
return RepoPermission.fromSyntax(syntax)
|
|
156
193
|
}
|
|
157
|
-
if (
|
|
158
|
-
return
|
|
194
|
+
if (isScopeSyntaxFor(syntax, 'rpc')) {
|
|
195
|
+
return RpcPermission.fromSyntax(syntax)
|
|
159
196
|
}
|
|
160
197
|
return null
|
|
161
198
|
}
|
|
162
199
|
|
|
163
|
-
function
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
): permission is P & { resource: T } {
|
|
200
|
+
function isLexPermissionForResource<
|
|
201
|
+
P extends { resource: unknown },
|
|
202
|
+
T extends string,
|
|
203
|
+
>(permission: P, type: T): permission is P & { resource: T } {
|
|
167
204
|
return permission.resource === type
|
|
168
205
|
}
|