@atproto/oauth-scopes 0.2.2 → 0.3.1

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 CHANGED
@@ -1,5 +1,36 @@
1
1
  # @atproto/oauth-scopes
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`d54d707`](https://github.com/bluesky-social/atproto/commit/d54d7077eb32041e1f61c312efa1dd0d768c774e), [`d54d707`](https://github.com/bluesky-social/atproto/commit/d54d7077eb32041e1f61c312efa1dd0d768c774e)]:
8
+ - @atproto/did@0.3.0
9
+
10
+ ## 0.3.0
11
+
12
+ ### Minor Changes
13
+
14
+ - [#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`
15
+
16
+ - [#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.
17
+
18
+ ### Patch Changes
19
+
20
+ - [#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`
21
+
22
+ - [#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
23
+
24
+ - [#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`
25
+
26
+ - [#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
27
+
28
+ - [#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
29
+
30
+ - 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)]:
31
+ - @atproto/did@0.2.3
32
+ - @atproto/syntax@0.4.2
33
+
3
34
  ## 0.2.2
4
35
 
5
36
  ### Patch Changes
package/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  Dual MIT/Apache-2.0 License
2
2
 
3
- Copyright (c) 2022-2025 Bluesky Social PBC, and Contributors
3
+ Copyright (c) 2022-2026 Bluesky Social PBC, and Contributors
4
4
 
5
5
  Except as otherwise noted in individual files, this software is licensed under the MIT license (<http://opensource.org/licenses/MIT>), or the Apache License, Version 2.0 (<http://www.apache.org/licenses/LICENSE-2.0>).
6
6
 
@@ -1,2 +1,19 @@
1
- export type { LexPermission, LexPermissionSet } from '@atproto/lexicon';
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,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA"}
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"}
@@ -1 +1 @@
1
- {"version":3,"file":"lexicon.js","sourceRoot":"","sources":["../../src/lib/lexicon.ts"],"names":[],"mappings":"","sourcesContent":["export type { LexPermission, LexPermissionSet } from '@atproto/lexicon'\n"]}
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 { LexPermission } from './lexicon.js';
1
+ import { LexiconPermission } from './lexicon.js';
2
2
  import { ScopeSyntax } from './syntax.js';
3
3
  /**
4
- * Translates a {@link LexPermission} into a {@link ScopeSyntax}.
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: Readonly<LexPermission & {
8
- resource: P;
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): string | number | boolean | (string | number | boolean)[] | undefined;
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): string | number | boolean | null | undefined;
18
- getMulti(key: string): (string | number | boolean)[] | null | undefined;
19
- toJSON(): Readonly<{
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,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC;;GAEG;AACH,qBAAa,mBAAmB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CACxD,YAAW,WAAW,CAAC,CAAC,CAAC;IAGvB,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,aAAa,GAAG;QAAE,QAAQ,EAAE,CAAC,CAAA;KAAE,CAAC;gBAAxD,aAAa,EAAE,QAAQ,CAAC,aAAa,GAAG;QAAE,QAAQ,EAAE,CAAC,CAAA;KAAE,CAAC;IAGnE,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;;;;kBAzCyD,CAAC;;CA4CjE"}
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 LexPermission} into a {@link ScopeSyntax}.
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 (Array.isArray(value))
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 (!Array.isArray(value))
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,YACW,aAAwD;QAAjE;;;;mBAAS,aAAa;WAA2C;IAChE,CAAC;IAEJ,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,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QACrC,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,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QACtC,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,aAAa,CAAA;IAC3B,CAAC;CACF;AAhDD,kDAgDC","sourcesContent":["import { LexPermission } from './lexicon.js'\nimport { ScopeSyntax } from './syntax.js'\n\n/**\n * Translates a {@link LexPermission} into a {@link ScopeSyntax}.\n */\nexport class LexPermissionSyntax<P extends string = string>\n implements ScopeSyntax<P>\n{\n constructor(\n readonly lexPermission: Readonly<LexPermission & { resource: P }>,\n ) {}\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 (Array.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 (!Array.isArray(value)) return null\n return value\n }\n\n toJSON() {\n return this.lexPermission\n }\n}\n"]}
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"]}
@@ -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
@@ -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;CACvD"}
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"}
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"syntax.js","sourceRoot":"","sources":["../../src/lib/syntax.ts"],"names":[],"mappings":";;AAiBA,4CAiBC;AApBD;;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","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): ParamValue[] | null | undefined\n}\n"]}
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: false;
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;AAGhE,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,aAAa;gBADrB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,aAAa;IAGvC,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;CAGtD"}
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 === 'manage' || this.action === options.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(options);
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: false,
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,gDAAgE;AAChE,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,MAAqB;QADrC;;;;mBAAgB,IAAI;WAAkB;QACtC;;;;mBAAgB,MAAM;WAAe;IACpC,CAAC;IAEJ,OAAO,CAAC,OAA+B;QACrC,OAAO,CACL,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI;YAC1B,CAAC,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAC7D,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,OAAO,CAAC,CAAA;IACjD,CAAC;;AApDH,8CAqDC;AAlC2B;;;;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,KAAK;YACf,QAAQ,EAAE,KAAK;YACf,QAAQ,EAAE,IAAA,8BAAoB,EAAC,uBAAe,CAAC;YAC/C,OAAO,EAAE,MAAe;SACzB;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 { 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: AccountAction,\n ) {}\n\n matches(options: AccountPermissionMatch) {\n return (\n this.attr === options.attr &&\n (this.action === 'manage' || this.action === 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: false,\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(options)\n }\n}\n"]}
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 { LexPermission, LexPermissionSet } from '../lib/lexicon.js';
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 LexPermission, type LexPermissionSet, type Nsid, isNsid };
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(): import("../lib/syntax.js").ScopeStringFor<"include">;
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
- toPermissions(permissionSet: LexPermissionSet): (BlobPermission | RepoPermission | RpcPermission)[];
25
- protected parsePermission(permission: LexPermission): BlobPermission | RepoPermission | RpcPermission | null;
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: unknown): permission is RpcPermission | RepoPermission | BlobPermission;
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,aAAa,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACnE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAGzC,OAAO,EAAE,WAAW,EAAoB,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,gBAAgB,EAAE,KAAK,IAAI,EAAE,MAAM,EAAE,CAAA;AAEvE;;;;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;;;OAGG;IACH,aAAa,CAAC,aAAa,EAAE,gBAAgB;IAM7C,SAAS,CAAC,eAAe,CAAC,UAAU,EAAE,aAAa;IAoBnD;;;;;OAKG;IACH,SAAS,CAAC,mBAAmB,CAC3B,UAAU,EAAE,OAAO,GAClB,UAAU,IAAI,aAAa,GAAG,cAAc,GAAG,cAAc;IAgBhE;;;;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"}
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
- toPermissions(permissionSet) {
42
- return permissionSet.permissions
43
- .map(this.parsePermission, this)
44
- .filter(this.isAllowedPermission, this);
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
- parsePermission(permission) {
47
- if (permission.resource === 'rpc' &&
48
- permission.inheritAud === true &&
49
- permission.aud === undefined &&
50
- this.aud !== undefined) {
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
- return parsePermission({
55
- ...permission,
56
- inheritAud: undefined,
57
- aud: this.aud,
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 parsePermission(permission);
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
- if (permission instanceof blob_permission_js_1.BlobPermission) {
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 parsePermission(permission) {
142
- if (isPermissionForResource(permission, 'repo')) {
143
- return repo_permission_js_1.RepoPermission.fromSyntax(new syntax_lexicon_js_1.LexPermissionSyntax(permission));
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 (isPermissionForResource(permission, 'blob')) {
149
- return blob_permission_js_1.BlobPermission.fromSyntax(new syntax_lexicon_js_1.LexPermissionSyntax(permission));
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 isPermissionForResource(permission, type) {
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.2.2",
3
+ "version": "0.3.1",
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.2",
29
- "@atproto/lexicon": "^0.5.2",
30
- "@atproto/syntax": "^0.4.1"
28
+ "@atproto/did": "^0.3.0",
29
+ "@atproto/syntax": "^0.4.3"
31
30
  },
32
31
  "devDependencies": {
33
32
  "jest": "^28.1.2",
@@ -1 +1,21 @@
1
- export type { LexPermission, LexPermissionSet } from '@atproto/lexicon'
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 { LexPermission } from './lexicon.js'
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 LexPermission} into a {@link ScopeSyntax}.
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 (Array.isArray(value)) return null
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 (!Array.isArray(value)) return null
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).toBe('read')
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).toBe('manage')
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).toBe('read')
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 === 'manage' || this.action === options.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: false,
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(options)
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, LexPermissionSet } from './include-scope'
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('toPermissions', () => {
160
+ describe('buildPermissions', () => {
161
161
  /**
162
- * Utility that transforms an "include:<nsid>" scope and matching
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: LexPermissionSet,
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('enables', () => {
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(['blob:image/*'])
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('valid permissions', () => {
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: 'did:web:example.com#foo',
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('valid inherited-aud permissions', () => {
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 { LexPermission, LexPermissionSet } from '../lib/lexicon.js'
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 { ScopeSyntax, isScopeStringFor } from '../lib/syntax.js'
8
- import { BlobPermission } from './blob-permission.js'
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 LexPermission, type LexPermissionSet, type Nsid, isNsid }
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
- toPermissions(permissionSet: LexPermissionSet) {
34
- return permissionSet.permissions
35
- .map(this.parsePermission, this)
36
- .filter(this.isAllowedPermission, this)
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 parsePermission(permission: LexPermission) {
40
- if (
41
- permission.resource === 'rpc' &&
42
- permission.inheritAud === true &&
43
- permission.aud === undefined &&
44
- this.aud !== undefined
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
- return parsePermission({
50
- ...permission,
51
- inheritAud: undefined,
52
- aud: this.aud,
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 parsePermission(permission)
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: unknown,
67
- ): permission is RpcPermission | RepoPermission | BlobPermission {
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
- if (permission instanceof BlobPermission) {
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 parsePermission(permission: LexPermission) {
151
- if (isPermissionForResource(permission, 'repo')) {
152
- return RepoPermission.fromSyntax(new LexPermissionSyntax(permission))
153
- }
154
- if (isPermissionForResource(permission, 'rpc')) {
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 (isPermissionForResource(permission, 'blob')) {
158
- return BlobPermission.fromSyntax(new LexPermissionSyntax(permission))
194
+ if (isScopeSyntaxFor(syntax, 'rpc')) {
195
+ return RpcPermission.fromSyntax(syntax)
159
196
  }
160
197
  return null
161
198
  }
162
199
 
163
- function isPermissionForResource<P extends LexPermission, T extends string>(
164
- permission: P,
165
- type: T,
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
  }
@@ -1 +0,0 @@
1
- {"root":["./src/atproto-oauth-scope.ts","./src/index.ts","./src/scope-missing-error.ts","./src/scope-permissions-transition.test.ts","./src/scope-permissions-transition.ts","./src/scope-permissions.test.ts","./src/scope-permissions.ts","./src/scopes-set.test.ts","./src/scopes-set.ts","./src/lib/lexicon.ts","./src/lib/mime.test.ts","./src/lib/mime.ts","./src/lib/nsid.ts","./src/lib/parser.ts","./src/lib/resource-permission.ts","./src/lib/syntax-lexicon.ts","./src/lib/syntax-string.test.ts","./src/lib/syntax-string.ts","./src/lib/syntax.test.ts","./src/lib/syntax.ts","./src/lib/util.ts","./src/scopes/account-permission.test.ts","./src/scopes/account-permission.ts","./src/scopes/blob-permission.test.ts","./src/scopes/blob-permission.ts","./src/scopes/identity-permission.test.ts","./src/scopes/identity-permission.ts","./src/scopes/include-scope.test.ts","./src/scopes/include-scope.ts","./src/scopes/repo-permission.test.ts","./src/scopes/repo-permission.ts","./src/scopes/rpc-permission.test.ts","./src/scopes/rpc-permission.ts"],"version":"5.8.3"}