@sasjs/api 0.7.2 → 0.9.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/build/browser.js +178 -111
- package/build/cjs/corpus/records.js +83 -81
- package/build/cjs/host/dispatch.js +12 -0
- package/build/cjs/identity/fixture.js +1 -1
- package/build/cjs/identity/records.js +46 -20
- package/build/cjs/identity/routeAccess.js +77 -0
- package/build/cjs/index.js +2 -0
- package/build/cjs/routes/overlay.js +4 -9
- package/build/esm/corpus/records.d.ts.map +1 -1
- package/build/esm/corpus/records.js +83 -81
- package/build/esm/host/dispatch.d.ts.map +1 -1
- package/build/esm/host/dispatch.js +12 -0
- package/build/esm/identity/fixture.js +1 -1
- package/build/esm/identity/records.d.ts +33 -3
- package/build/esm/identity/records.d.ts.map +1 -1
- package/build/esm/identity/records.js +46 -20
- package/build/esm/identity/routeAccess.d.ts +57 -0
- package/build/esm/identity/routeAccess.d.ts.map +1 -0
- package/build/esm/identity/routeAccess.js +72 -0
- package/build/esm/index.d.ts +2 -1
- package/build/esm/index.d.ts.map +1 -1
- package/build/esm/index.js +2 -0
- package/build/esm/routes/overlay.d.ts.map +1 -1
- package/build/esm/routes/overlay.js +4 -9
- package/package.json +1 -1
|
@@ -401,7 +401,7 @@ export const RECORD_CORPUS = [
|
|
|
401
401
|
request: {
|
|
402
402
|
token: RESTRICTED,
|
|
403
403
|
params: { username: 'user2' },
|
|
404
|
-
body: { password: '
|
|
404
|
+
body: { password: 'wrong123' }
|
|
405
405
|
},
|
|
406
406
|
expect: { status: 401, error: 'Invalid password.' }
|
|
407
407
|
},
|
|
@@ -699,6 +699,70 @@ export const RECORD_CORPUS = [
|
|
|
699
699
|
expect: { status: 404, error: 'Group not found.' }
|
|
700
700
|
},
|
|
701
701
|
// ------------------------------------------------------------ permissions
|
|
702
|
+
{
|
|
703
|
+
id: 'authorized-routes',
|
|
704
|
+
route: 'GET /SASjsApi/info/authorizedRoutes',
|
|
705
|
+
description: 'answers the routes that accept a rule, to any caller',
|
|
706
|
+
identity: ADMIN_SIGNED_IN,
|
|
707
|
+
request: { token: RESTRICTED },
|
|
708
|
+
expect: {
|
|
709
|
+
status: 200,
|
|
710
|
+
body: {
|
|
711
|
+
paths: [
|
|
712
|
+
'/AppStream',
|
|
713
|
+
'/SASjsApi',
|
|
714
|
+
'/SASjsApi/code/execute',
|
|
715
|
+
'/SASjsApi/stp/execute',
|
|
716
|
+
'/SASjsApi/drive/deploy',
|
|
717
|
+
'/SASjsApi/drive/deploy/upload',
|
|
718
|
+
'/SASjsApi/drive/file',
|
|
719
|
+
'/SASjsApi/drive/folder',
|
|
720
|
+
'/SASjsApi/drive/fileTree',
|
|
721
|
+
'/SASjsApi/drive/rename'
|
|
722
|
+
]
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
id: 'my-authorized-routes-as-admin',
|
|
728
|
+
route: 'GET /SASjsApi/info/myAuthorizedRoutes',
|
|
729
|
+
description: 'answers every route to an administrator',
|
|
730
|
+
identity: ADMIN_SIGNED_IN,
|
|
731
|
+
request: { token: ADMIN },
|
|
732
|
+
expect: {
|
|
733
|
+
status: 200,
|
|
734
|
+
body: {
|
|
735
|
+
paths: [
|
|
736
|
+
'/AppStream',
|
|
737
|
+
'/SASjsApi',
|
|
738
|
+
'/SASjsApi/code/execute',
|
|
739
|
+
'/SASjsApi/stp/execute',
|
|
740
|
+
'/SASjsApi/drive/deploy',
|
|
741
|
+
'/SASjsApi/drive/deploy/upload',
|
|
742
|
+
'/SASjsApi/drive/file',
|
|
743
|
+
'/SASjsApi/drive/folder',
|
|
744
|
+
'/SASjsApi/drive/fileTree',
|
|
745
|
+
'/SASjsApi/drive/rename'
|
|
746
|
+
]
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
id: 'my-authorized-routes-as-user',
|
|
752
|
+
route: 'GET /SASjsApi/info/myAuthorizedRoutes',
|
|
753
|
+
description: 'answers a caller only the routes its rules grant',
|
|
754
|
+
identity: ADMIN_SIGNED_IN,
|
|
755
|
+
request: { token: RESTRICTED },
|
|
756
|
+
expect: {
|
|
757
|
+
status: 200,
|
|
758
|
+
// /AppStream is visible whatever the rules say; the two grants come from
|
|
759
|
+
// DCGroup; /SASjsApi/code/execute is denied to this user by a user rule,
|
|
760
|
+
// and nothing grants the rest
|
|
761
|
+
body: {
|
|
762
|
+
paths: ['/AppStream', '/SASjsApi/stp/execute', '/SASjsApi/drive/file']
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
},
|
|
702
766
|
{
|
|
703
767
|
id: 'list-permissions-as-admin',
|
|
704
768
|
route: 'GET /SASjsApi/permission',
|
|
@@ -710,55 +774,24 @@ export const RECORD_CORPUS = [
|
|
|
710
774
|
body: [
|
|
711
775
|
{
|
|
712
776
|
uid: 'rule-routes',
|
|
713
|
-
path: '/SASjsApi/
|
|
777
|
+
path: '/SASjsApi/drive/file',
|
|
714
778
|
type: 'Route',
|
|
715
779
|
setting: 'Grant',
|
|
716
|
-
group:
|
|
717
|
-
uid: 'group-data-controller',
|
|
718
|
-
name: 'DCGroup',
|
|
719
|
-
description: 'Data Controller users',
|
|
720
|
-
isActive: true,
|
|
721
|
-
users: [
|
|
722
|
-
{
|
|
723
|
-
uid: RESTRICTED_UID,
|
|
724
|
-
username: 'user2',
|
|
725
|
-
displayName: 'Demo User',
|
|
726
|
-
isAdmin: false
|
|
727
|
-
}
|
|
728
|
-
]
|
|
729
|
-
}
|
|
780
|
+
group: 'group-data-controller'
|
|
730
781
|
},
|
|
731
782
|
{
|
|
732
783
|
uid: 'rule-execute',
|
|
733
784
|
path: '/SASjsApi/stp/execute',
|
|
734
785
|
type: 'Route',
|
|
735
786
|
setting: 'Grant',
|
|
736
|
-
group:
|
|
737
|
-
uid: 'group-data-controller',
|
|
738
|
-
name: 'DCGroup',
|
|
739
|
-
description: 'Data Controller users',
|
|
740
|
-
isActive: true,
|
|
741
|
-
users: [
|
|
742
|
-
{
|
|
743
|
-
uid: RESTRICTED_UID,
|
|
744
|
-
username: 'user2',
|
|
745
|
-
displayName: 'Demo User',
|
|
746
|
-
isAdmin: false
|
|
747
|
-
}
|
|
748
|
-
]
|
|
749
|
-
}
|
|
787
|
+
group: 'group-data-controller'
|
|
750
788
|
},
|
|
751
789
|
{
|
|
752
790
|
uid: 'rule-code',
|
|
753
791
|
path: '/SASjsApi/code/execute',
|
|
754
792
|
type: 'Route',
|
|
755
793
|
setting: 'Deny',
|
|
756
|
-
user:
|
|
757
|
-
uid: RESTRICTED_UID,
|
|
758
|
-
username: 'user2',
|
|
759
|
-
displayName: 'Demo User',
|
|
760
|
-
isAdmin: false
|
|
761
|
-
}
|
|
794
|
+
user: RESTRICTED_UID
|
|
762
795
|
}
|
|
763
796
|
]
|
|
764
797
|
}
|
|
@@ -774,55 +807,24 @@ export const RECORD_CORPUS = [
|
|
|
774
807
|
body: [
|
|
775
808
|
{
|
|
776
809
|
uid: 'rule-routes',
|
|
777
|
-
path: '/SASjsApi/
|
|
810
|
+
path: '/SASjsApi/drive/file',
|
|
778
811
|
type: 'Route',
|
|
779
812
|
setting: 'Grant',
|
|
780
|
-
group:
|
|
781
|
-
uid: 'group-data-controller',
|
|
782
|
-
name: 'DCGroup',
|
|
783
|
-
description: 'Data Controller users',
|
|
784
|
-
isActive: true,
|
|
785
|
-
users: [
|
|
786
|
-
{
|
|
787
|
-
uid: RESTRICTED_UID,
|
|
788
|
-
username: 'user2',
|
|
789
|
-
displayName: 'Demo User',
|
|
790
|
-
isAdmin: false
|
|
791
|
-
}
|
|
792
|
-
]
|
|
793
|
-
}
|
|
813
|
+
group: 'group-data-controller'
|
|
794
814
|
},
|
|
795
815
|
{
|
|
796
816
|
uid: 'rule-execute',
|
|
797
817
|
path: '/SASjsApi/stp/execute',
|
|
798
818
|
type: 'Route',
|
|
799
819
|
setting: 'Grant',
|
|
800
|
-
group:
|
|
801
|
-
uid: 'group-data-controller',
|
|
802
|
-
name: 'DCGroup',
|
|
803
|
-
description: 'Data Controller users',
|
|
804
|
-
isActive: true,
|
|
805
|
-
users: [
|
|
806
|
-
{
|
|
807
|
-
uid: RESTRICTED_UID,
|
|
808
|
-
username: 'user2',
|
|
809
|
-
displayName: 'Demo User',
|
|
810
|
-
isAdmin: false
|
|
811
|
-
}
|
|
812
|
-
]
|
|
813
|
-
}
|
|
820
|
+
group: 'group-data-controller'
|
|
814
821
|
},
|
|
815
822
|
{
|
|
816
823
|
uid: 'rule-code',
|
|
817
824
|
path: '/SASjsApi/code/execute',
|
|
818
825
|
type: 'Route',
|
|
819
826
|
setting: 'Deny',
|
|
820
|
-
user:
|
|
821
|
-
uid: RESTRICTED_UID,
|
|
822
|
-
username: 'user2',
|
|
823
|
-
displayName: 'Demo User',
|
|
824
|
-
isAdmin: false
|
|
825
|
-
}
|
|
827
|
+
user: RESTRICTED_UID
|
|
826
828
|
}
|
|
827
829
|
]
|
|
828
830
|
}
|
|
@@ -843,7 +845,7 @@ export const RECORD_CORPUS = [
|
|
|
843
845
|
request: {
|
|
844
846
|
token: ADMIN,
|
|
845
847
|
body: {
|
|
846
|
-
path: '/SASjsApi/
|
|
848
|
+
path: '/SASjsApi/drive/folder',
|
|
847
849
|
type: 'Route',
|
|
848
850
|
setting: 'Grant',
|
|
849
851
|
principalType: 'user',
|
|
@@ -852,7 +854,7 @@ export const RECORD_CORPUS = [
|
|
|
852
854
|
},
|
|
853
855
|
expect: {
|
|
854
856
|
status: 200,
|
|
855
|
-
partial: { path: '/SASjsApi/
|
|
857
|
+
partial: { path: '/SASjsApi/drive/folder', setting: 'Grant' }
|
|
856
858
|
}
|
|
857
859
|
},
|
|
858
860
|
{
|
|
@@ -863,7 +865,7 @@ export const RECORD_CORPUS = [
|
|
|
863
865
|
request: {
|
|
864
866
|
token: ADMIN,
|
|
865
867
|
body: {
|
|
866
|
-
path: '/SASjsApi/
|
|
868
|
+
path: '/SASjsApi/drive/rename',
|
|
867
869
|
type: 'Route',
|
|
868
870
|
setting: 'Grant',
|
|
869
871
|
principalType: 'group',
|
|
@@ -873,9 +875,9 @@ export const RECORD_CORPUS = [
|
|
|
873
875
|
expect: {
|
|
874
876
|
status: 200,
|
|
875
877
|
partial: {
|
|
876
|
-
path: '/SASjsApi/
|
|
878
|
+
path: '/SASjsApi/drive/rename',
|
|
877
879
|
setting: 'Grant',
|
|
878
|
-
group:
|
|
880
|
+
group: 'group-data-controller'
|
|
879
881
|
}
|
|
880
882
|
}
|
|
881
883
|
},
|
|
@@ -887,7 +889,7 @@ export const RECORD_CORPUS = [
|
|
|
887
889
|
request: {
|
|
888
890
|
token: ADMIN,
|
|
889
891
|
body: {
|
|
890
|
-
path: '/SASjsApi/
|
|
892
|
+
path: '/SASjsApi/drive/deploy',
|
|
891
893
|
type: 'Route',
|
|
892
894
|
setting: 'Grant',
|
|
893
895
|
principalType: 'role',
|
|
@@ -907,7 +909,7 @@ export const RECORD_CORPUS = [
|
|
|
907
909
|
request: {
|
|
908
910
|
token: ADMIN,
|
|
909
911
|
body: {
|
|
910
|
-
path: '/SASjsApi/
|
|
912
|
+
path: '/SASjsApi/drive/folder',
|
|
911
913
|
type: 'Route',
|
|
912
914
|
setting: 'Grant',
|
|
913
915
|
principalType: 'user',
|
|
@@ -927,7 +929,7 @@ export const RECORD_CORPUS = [
|
|
|
927
929
|
request: {
|
|
928
930
|
token: RESTRICTED,
|
|
929
931
|
body: {
|
|
930
|
-
path: '/SASjsApi/
|
|
932
|
+
path: '/SASjsApi/drive/folder',
|
|
931
933
|
type: 'Route',
|
|
932
934
|
setting: 'Grant',
|
|
933
935
|
principalType: 'user',
|
|
@@ -944,7 +946,7 @@ export const RECORD_CORPUS = [
|
|
|
944
946
|
request: {
|
|
945
947
|
token: ADMIN,
|
|
946
948
|
body: {
|
|
947
|
-
path: '/SASjsApi/
|
|
949
|
+
path: '/SASjsApi/drive/rename',
|
|
948
950
|
type: 'Route',
|
|
949
951
|
setting: 'Grant',
|
|
950
952
|
principalType: 'group',
|
|
@@ -961,7 +963,7 @@ export const RECORD_CORPUS = [
|
|
|
961
963
|
request: {
|
|
962
964
|
token: ADMIN,
|
|
963
965
|
body: {
|
|
964
|
-
path: '/SASjsApi/
|
|
966
|
+
path: '/SASjsApi/drive/file',
|
|
965
967
|
type: 'Route',
|
|
966
968
|
setting: 'Grant',
|
|
967
969
|
principalType: 'group',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../../src/host/dispatch.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAOnD,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,oFAAoF;IACpF,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,CAAA;IACtD;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,mBAAmB;IACnB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;CAC/B;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,YAAY,CAAA;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAA;IAC1B,yFAAyF;IACzF,OAAO,CAAC,EAAE,aAAa,CAAA;CACxB;AAsCD,eAAO,MAAM,QAAQ,GACnB,UAAU,QAAQ,EAClB,SAAS,MAAM,EACf,UAAS,UAAe,KACvB,OAAO,CAAC,WAAW,
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../../src/host/dispatch.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAOnD,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,oFAAoF;IACpF,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,CAAA;IACtD;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,mBAAmB;IACnB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;CAC/B;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,YAAY,CAAA;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAA;IAC1B,yFAAyF;IACzF,OAAO,CAAC,EAAE,aAAa,CAAA;CACxB;AAsCD,eAAO,MAAM,QAAQ,GACnB,UAAU,QAAQ,EAClB,SAAS,MAAM,EACf,UAAS,UAAe,KACvB,OAAO,CAAC,WAAW,CAgXrB,CAAA;AAED,0DAA0D;AAC1D,eAAO,MAAM,cAAc,EAAE,MAAM,EA0ClC,CAAA"}
|
|
@@ -216,6 +216,16 @@ export const dispatch = async (services, routeId, request = {}) => {
|
|
|
216
216
|
status: 200,
|
|
217
217
|
body: await required().deleteUser(await caller(), { username: String(params.username ?? '') }, { password: body.password })
|
|
218
218
|
};
|
|
219
|
+
case 'GET /SASjsApi/info/authorizedRoutes':
|
|
220
|
+
return {
|
|
221
|
+
status: 200,
|
|
222
|
+
body: await required().listAuthorizedRoutes(await caller())
|
|
223
|
+
};
|
|
224
|
+
case 'GET /SASjsApi/info/myAuthorizedRoutes':
|
|
225
|
+
return {
|
|
226
|
+
status: 200,
|
|
227
|
+
body: await required().myAuthorizedRoutes(await caller())
|
|
228
|
+
};
|
|
219
229
|
case 'GET /SASjsApi/group':
|
|
220
230
|
return { status: 200, body: await required().listGroups(await caller()) };
|
|
221
231
|
case 'GET /SASjsApi/group/{uid}':
|
|
@@ -318,6 +328,8 @@ export const dispatch = async (services, routeId, request = {}) => {
|
|
|
318
328
|
};
|
|
319
329
|
/** Whether a route is one `dispatch` can answer today. */
|
|
320
330
|
export const HANDLED_ROUTES = [
|
|
331
|
+
'GET /SASjsApi/info/authorizedRoutes',
|
|
332
|
+
'GET /SASjsApi/info/myAuthorizedRoutes',
|
|
321
333
|
'GET /SASjsApi/drive/file',
|
|
322
334
|
'POST /SASjsApi/drive/file',
|
|
323
335
|
'PATCH /SASjsApi/drive/file',
|
|
@@ -25,13 +25,28 @@ export interface GroupDetailsResponse extends GroupResponse {
|
|
|
25
25
|
isActive: boolean;
|
|
26
26
|
users: UserResponse[];
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* A permission rule as the routes report one.
|
|
30
|
+
*
|
|
31
|
+
* The principal is the identifier, not the whole account: a rule is a row in a
|
|
32
|
+
* table, and a host on any database can produce `user` or `group` as a uid
|
|
33
|
+
* without a join or a populate step. A client that wants the name asks for the
|
|
34
|
+
* account it names, which it can do whether the store is Mongo, a browser's
|
|
35
|
+
* IndexedDB or a file.
|
|
36
|
+
*/
|
|
28
37
|
export interface PermissionResponse {
|
|
29
38
|
uid: string;
|
|
30
39
|
path: string;
|
|
31
40
|
type: string;
|
|
32
41
|
setting: string;
|
|
33
|
-
user
|
|
34
|
-
|
|
42
|
+
/** The uid of the account the rule is assigned to, when it is a user's. */
|
|
43
|
+
user?: string;
|
|
44
|
+
/** The uid of the group the rule is assigned to, when it is a group's. */
|
|
45
|
+
group?: string;
|
|
46
|
+
}
|
|
47
|
+
/** What `authorizedRoutes` and `myAuthorizedRoutes` answer. */
|
|
48
|
+
export interface AuthorizedRoutesResponse {
|
|
49
|
+
paths: string[];
|
|
35
50
|
}
|
|
36
51
|
export interface RecordService {
|
|
37
52
|
listUsers(caller: Subject | undefined): Promise<UserResponse[]>;
|
|
@@ -77,6 +92,13 @@ export interface RecordService {
|
|
|
77
92
|
deleteGroup(caller: Subject | undefined, uid: string): Promise<void>;
|
|
78
93
|
addUserToGroup(caller: Subject | undefined, groupUid: string, userUid: string): Promise<GroupDetailsResponse>;
|
|
79
94
|
removeUserFromGroup(caller: Subject | undefined, groupUid: string, userUid: string): Promise<GroupDetailsResponse>;
|
|
95
|
+
/**
|
|
96
|
+
* The routes that accept a rule. Any authenticated caller may read it: it is
|
|
97
|
+
* the inventory, not a statement about what this caller holds.
|
|
98
|
+
*/
|
|
99
|
+
listAuthorizedRoutes(caller: Subject | undefined): Promise<AuthorizedRoutesResponse>;
|
|
100
|
+
/** The routes this caller may use, from the rules that bear on them. */
|
|
101
|
+
myAuthorizedRoutes(caller: Subject | undefined): Promise<AuthorizedRoutesResponse>;
|
|
80
102
|
listPermissions(caller: Subject | undefined): Promise<PermissionResponse[]>;
|
|
81
103
|
createPermission(caller: Subject | undefined, body: {
|
|
82
104
|
path: string;
|
|
@@ -98,5 +120,13 @@ export interface RecordService {
|
|
|
98
120
|
}): Promise<void>;
|
|
99
121
|
}
|
|
100
122
|
export declare const sanitiseDisplayName: (value: unknown) => string;
|
|
101
|
-
export
|
|
123
|
+
export interface RecordServiceOptions {
|
|
124
|
+
/**
|
|
125
|
+
* The routes that accept a permission rule. Defaults to the ones this package
|
|
126
|
+
* knows; a host that streams applications adds `/AppStream/<app>` for each,
|
|
127
|
+
* because that set is the host's configuration and not a fact about the API.
|
|
128
|
+
*/
|
|
129
|
+
grantableRoutes?: string[];
|
|
130
|
+
}
|
|
131
|
+
export declare const createRecordService: (store: IdentityStore, options?: RecordServiceOptions) => RecordService;
|
|
102
132
|
//# sourceMappingURL=records.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../../src/identity/records.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../../src/identity/records.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* the server sends.
|
|
15
15
|
*/
|
|
16
16
|
import { ApiError } from '../types.js';
|
|
17
|
+
import { ALWAYS_VISIBLE_ROUTES, DEFAULT_GRANTABLE_ROUTES, isRouteGranted, topLevelRouteFor } from './routeAccess.js';
|
|
17
18
|
/** The name the server gives the group every account is added to on creation. */
|
|
18
19
|
const ALL_USERS_GROUP = 'all-users';
|
|
19
20
|
/** The group whose membership the API refuses to change. */
|
|
@@ -79,7 +80,8 @@ const asGroup = (group) => ({
|
|
|
79
80
|
name: group.name,
|
|
80
81
|
description: group.description
|
|
81
82
|
});
|
|
82
|
-
export const createRecordService = (store) => {
|
|
83
|
+
export const createRecordService = (store, options = {}) => {
|
|
84
|
+
const grantableRoutes = options.grantableRoutes || DEFAULT_GRANTABLE_ROUTES;
|
|
83
85
|
const groupDetails = async (group) => {
|
|
84
86
|
const members = [];
|
|
85
87
|
for (const uid of group.users) {
|
|
@@ -109,25 +111,14 @@ export const createRecordService = (store) => {
|
|
|
109
111
|
const findBy = async (by) => by.uid !== undefined
|
|
110
112
|
? store.userById(by.uid)
|
|
111
113
|
: store.userByName(by.username ?? '');
|
|
112
|
-
const permissionDetails = async (permission) => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const user = await store.userById(permission.user);
|
|
121
|
-
if (user)
|
|
122
|
-
response.user = asUser(user);
|
|
123
|
-
}
|
|
124
|
-
if (permission.group) {
|
|
125
|
-
const group = await store.groupById(permission.group);
|
|
126
|
-
if (group)
|
|
127
|
-
response.group = await groupDetails(group);
|
|
128
|
-
}
|
|
129
|
-
return response;
|
|
130
|
-
};
|
|
114
|
+
const permissionDetails = async (permission) => ({
|
|
115
|
+
uid: permission.uid,
|
|
116
|
+
path: permission.path,
|
|
117
|
+
type: permission.type,
|
|
118
|
+
setting: permission.setting,
|
|
119
|
+
...(permission.user ? { user: permission.user } : {}),
|
|
120
|
+
...(permission.group ? { group: permission.group } : {})
|
|
121
|
+
});
|
|
131
122
|
return {
|
|
132
123
|
async listUsers(caller) {
|
|
133
124
|
await requireCaller(store, caller);
|
|
@@ -292,6 +283,41 @@ export const createRecordService = (store) => {
|
|
|
292
283
|
throw new ApiError(400, 'Unable to update group.');
|
|
293
284
|
return groupDetails(updated);
|
|
294
285
|
},
|
|
286
|
+
async listAuthorizedRoutes(caller) {
|
|
287
|
+
// an identity is required because the route is about a caller's access,
|
|
288
|
+
// even though what it returns is the whole inventory
|
|
289
|
+
await requireCaller(store, caller);
|
|
290
|
+
return { paths: [...grantableRoutes] };
|
|
291
|
+
},
|
|
292
|
+
async myAuthorizedRoutes(caller) {
|
|
293
|
+
const reader = await requireCaller(store, caller);
|
|
294
|
+
// an administrator holds every route, and the always-visible ones are
|
|
295
|
+
// visible whatever the rules say
|
|
296
|
+
if (reader.isAdmin)
|
|
297
|
+
return { paths: [...grantableRoutes] };
|
|
298
|
+
const rules = await store.permissions();
|
|
299
|
+
const mine = {
|
|
300
|
+
user: rules
|
|
301
|
+
.filter((rule) => rule.user === reader.uid)
|
|
302
|
+
.map((rule) => ({ path: rule.path, setting: rule.setting })),
|
|
303
|
+
group: rules
|
|
304
|
+
.filter((rule) => rule.group !== undefined && reader.groups.includes(rule.group))
|
|
305
|
+
.map((rule) => ({ path: rule.path, setting: rule.setting }))
|
|
306
|
+
};
|
|
307
|
+
// a grant on the reserved public group admits any caller, and it is
|
|
308
|
+
// checked before the rules so no user or group rule can take it away
|
|
309
|
+
const publicGroup = (await store.groups()).find((group) => group.name === 'public');
|
|
310
|
+
const publicGrants = publicGroup
|
|
311
|
+
? rules
|
|
312
|
+
.filter((rule) => rule.group === publicGroup.uid && rule.setting === 'Grant')
|
|
313
|
+
.map((rule) => rule.path)
|
|
314
|
+
: [];
|
|
315
|
+
return {
|
|
316
|
+
paths: grantableRoutes.filter((path) => ALWAYS_VISIBLE_ROUTES.includes(path) ||
|
|
317
|
+
publicGrants.includes(path) ||
|
|
318
|
+
isRouteGranted(mine, path, topLevelRouteFor(path)))
|
|
319
|
+
};
|
|
320
|
+
},
|
|
295
321
|
async listPermissions(caller) {
|
|
296
322
|
const reader = await requireCaller(store, caller);
|
|
297
323
|
const rules = await store.permissions();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which routes a caller may use, from the permission rules alone.
|
|
3
|
+
*
|
|
4
|
+
* This is the answer `GET /SASjsApi/info/myAuthorizedRoutes` gives, and it is the
|
|
5
|
+
* same decision the gate makes when the route is actually requested - a
|
|
6
|
+
* web interface that offers a route the gate then refuses is the bug this exists
|
|
7
|
+
* to prevent, so the two must not be two implementations. A host that gates its
|
|
8
|
+
* own middleware should call `isRouteGranted` rather than re-derive it.
|
|
9
|
+
*/
|
|
10
|
+
/** The two top-level routes, which a rule can address to cover everything below. */
|
|
11
|
+
export declare const TOP_LEVEL_ROUTES: string[];
|
|
12
|
+
/**
|
|
13
|
+
* Routes any authenticated caller may use whatever the rules say. `/AppStream` is
|
|
14
|
+
* the navigation page whose job is to list the apps the caller may open, so
|
|
15
|
+
* gating it on a grant that lists them would hide the page that leads to them.
|
|
16
|
+
*/
|
|
17
|
+
export declare const ALWAYS_VISIBLE_ROUTES: string[];
|
|
18
|
+
/**
|
|
19
|
+
* The routes that accept a rule, before a host adds its own. A host with
|
|
20
|
+
* streamed applications extends this with `/AppStream/<app>` for each one, which
|
|
21
|
+
* is why the inventory is passed in rather than derived from a fixed list here.
|
|
22
|
+
*/
|
|
23
|
+
export declare const STATIC_AUTHORIZED_ROUTES: string[];
|
|
24
|
+
/** The route inventory when the host states no additions of its own. */
|
|
25
|
+
export declare const DEFAULT_GRANTABLE_ROUTES: string[];
|
|
26
|
+
export interface RouteRule {
|
|
27
|
+
path: string;
|
|
28
|
+
setting: string;
|
|
29
|
+
}
|
|
30
|
+
/** The rules that can bear on one caller. */
|
|
31
|
+
export interface RouteRules {
|
|
32
|
+
user: RouteRule[];
|
|
33
|
+
group: RouteRule[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The top-level route that governs a request: `/SASjsApi/code/execute` is
|
|
37
|
+
* governed by `/SASjsApi`, and a rule on the top-level route applies to
|
|
38
|
+
* everything beneath it.
|
|
39
|
+
*/
|
|
40
|
+
export declare const topLevelRouteFor: (routePath: string) => string;
|
|
41
|
+
/**
|
|
42
|
+
* Whether these rules grant access to `path`.
|
|
43
|
+
*
|
|
44
|
+
* The order is the documented one and the only implementation of it:
|
|
45
|
+
*
|
|
46
|
+
* 1. a rule addressed to the user on the path itself decides either way;
|
|
47
|
+
* 2. otherwise a rule addressed to the user on the top-level route decides;
|
|
48
|
+
* 3. otherwise a group grant on the path allows, and a group deny on the path
|
|
49
|
+
* blocks the top-level check - but a grant from another group still allows;
|
|
50
|
+
* 4. otherwise a group grant on the top-level route allows;
|
|
51
|
+
* 5. otherwise denied.
|
|
52
|
+
*
|
|
53
|
+
* A grant beats a deny at the same level, which is why step 3 looks at every
|
|
54
|
+
* group rule rather than stopping at the first deny.
|
|
55
|
+
*/
|
|
56
|
+
export declare const isRouteGranted: (rules: RouteRules, path: string, topLevelRoute?: string) => boolean;
|
|
57
|
+
//# sourceMappingURL=routeAccess.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routeAccess.d.ts","sourceRoot":"","sources":["../../../src/identity/routeAccess.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,UAA8B,CAAA;AAE3D;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,UAAiB,CAAA;AAEnD;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,UASpC,CAAA;AAED,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,UAGpC,CAAA;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,6CAA6C;AAC7C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,SAAS,EAAE,CAAA;IACjB,KAAK,EAAE,SAAS,EAAE,CAAA;CACnB;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,WAAW,MAAM,KAAG,MAGrC,CAAA;AAQhB;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,GACzB,OAAO,UAAU,EACjB,MAAM,MAAM,EACZ,gBAAe,MAA+B,KAC7C,OAcF,CAAA"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which routes a caller may use, from the permission rules alone.
|
|
3
|
+
*
|
|
4
|
+
* This is the answer `GET /SASjsApi/info/myAuthorizedRoutes` gives, and it is the
|
|
5
|
+
* same decision the gate makes when the route is actually requested - a
|
|
6
|
+
* web interface that offers a route the gate then refuses is the bug this exists
|
|
7
|
+
* to prevent, so the two must not be two implementations. A host that gates its
|
|
8
|
+
* own middleware should call `isRouteGranted` rather than re-derive it.
|
|
9
|
+
*/
|
|
10
|
+
/** The two top-level routes, which a rule can address to cover everything below. */
|
|
11
|
+
export const TOP_LEVEL_ROUTES = ['/AppStream', '/SASjsApi'];
|
|
12
|
+
/**
|
|
13
|
+
* Routes any authenticated caller may use whatever the rules say. `/AppStream` is
|
|
14
|
+
* the navigation page whose job is to list the apps the caller may open, so
|
|
15
|
+
* gating it on a grant that lists them would hide the page that leads to them.
|
|
16
|
+
*/
|
|
17
|
+
export const ALWAYS_VISIBLE_ROUTES = ['/AppStream'];
|
|
18
|
+
/**
|
|
19
|
+
* The routes that accept a rule, before a host adds its own. A host with
|
|
20
|
+
* streamed applications extends this with `/AppStream/<app>` for each one, which
|
|
21
|
+
* is why the inventory is passed in rather than derived from a fixed list here.
|
|
22
|
+
*/
|
|
23
|
+
export const STATIC_AUTHORIZED_ROUTES = [
|
|
24
|
+
'/SASjsApi/code/execute',
|
|
25
|
+
'/SASjsApi/stp/execute',
|
|
26
|
+
'/SASjsApi/drive/deploy',
|
|
27
|
+
'/SASjsApi/drive/deploy/upload',
|
|
28
|
+
'/SASjsApi/drive/file',
|
|
29
|
+
'/SASjsApi/drive/folder',
|
|
30
|
+
'/SASjsApi/drive/fileTree',
|
|
31
|
+
'/SASjsApi/drive/rename'
|
|
32
|
+
];
|
|
33
|
+
/** The route inventory when the host states no additions of its own. */
|
|
34
|
+
export const DEFAULT_GRANTABLE_ROUTES = [
|
|
35
|
+
...TOP_LEVEL_ROUTES,
|
|
36
|
+
...STATIC_AUTHORIZED_ROUTES
|
|
37
|
+
];
|
|
38
|
+
/**
|
|
39
|
+
* The top-level route that governs a request: `/SASjsApi/code/execute` is
|
|
40
|
+
* governed by `/SASjsApi`, and a rule on the top-level route applies to
|
|
41
|
+
* everything beneath it.
|
|
42
|
+
*/
|
|
43
|
+
export const topLevelRouteFor = (routePath) => TOP_LEVEL_ROUTES.find((route) => routePath.toLowerCase().startsWith(route.toLowerCase())) ?? routePath;
|
|
44
|
+
const settingOn = (rules, path) => rules.find((rule) => rule.path === path)?.setting;
|
|
45
|
+
const GRANT = 'Grant';
|
|
46
|
+
const DENY = 'Deny';
|
|
47
|
+
/**
|
|
48
|
+
* Whether these rules grant access to `path`.
|
|
49
|
+
*
|
|
50
|
+
* The order is the documented one and the only implementation of it:
|
|
51
|
+
*
|
|
52
|
+
* 1. a rule addressed to the user on the path itself decides either way;
|
|
53
|
+
* 2. otherwise a rule addressed to the user on the top-level route decides;
|
|
54
|
+
* 3. otherwise a group grant on the path allows, and a group deny on the path
|
|
55
|
+
* blocks the top-level check - but a grant from another group still allows;
|
|
56
|
+
* 4. otherwise a group grant on the top-level route allows;
|
|
57
|
+
* 5. otherwise denied.
|
|
58
|
+
*
|
|
59
|
+
* A grant beats a deny at the same level, which is why step 3 looks at every
|
|
60
|
+
* group rule rather than stopping at the first deny.
|
|
61
|
+
*/
|
|
62
|
+
export const isRouteGranted = (rules, path, topLevelRoute = topLevelRouteFor(path)) => {
|
|
63
|
+
const userSetting = settingOn(rules.user, path) ?? settingOn(rules.user, topLevelRoute);
|
|
64
|
+
if (userSetting)
|
|
65
|
+
return userSetting === GRANT;
|
|
66
|
+
const onPath = rules.group.filter((rule) => rule.path === path);
|
|
67
|
+
if (onPath.some((rule) => rule.setting === GRANT))
|
|
68
|
+
return true;
|
|
69
|
+
if (onPath.some((rule) => rule.setting === DENY))
|
|
70
|
+
return false;
|
|
71
|
+
return rules.group.some((rule) => rule.path === topLevelRoute && rule.setting === GRANT);
|
|
72
|
+
};
|
package/build/esm/index.d.ts
CHANGED
|
@@ -25,10 +25,11 @@ export type { RouteMatch } from './routes/index.js';
|
|
|
25
25
|
export { OVERLAY, TAG_CAPABILITIES } from './routes/overlay.js';
|
|
26
26
|
export type { RouteOverlay } from './routes/overlay.js';
|
|
27
27
|
export { dispatch, HANDLED_ROUTES } from './host/dispatch.js';
|
|
28
|
+
export * from './identity/routeAccess.js';
|
|
28
29
|
export type { ApiRequest, ApiResponse, Services } from './host/dispatch.js';
|
|
29
30
|
export { createIdentityService } from './identity/service.js';
|
|
30
31
|
export { createRecordService, sanitiseDisplayName } from './identity/records.js';
|
|
31
|
-
export type { GroupDetailsResponse, GroupResponse, PermissionResponse, RecordService, UserDetailsResponse, UserResponse } from './identity/records.js';
|
|
32
|
+
export type { AuthorizedRoutesResponse, GroupDetailsResponse, GroupResponse, PermissionResponse, RecordServiceOptions, RecordService, UserDetailsResponse, UserResponse } from './identity/records.js';
|
|
32
33
|
export type { IdentityService, LoginResult, SessionResult } from './identity/service.js';
|
|
33
34
|
export { MemoryIdentityStore } from './identity/memory-store.js';
|
|
34
35
|
export { DEFAULT_IDENTITY, IDENTITY_FORMAT, parseIdentity } from './identity/fixture.js';
|
package/build/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,cAAc,YAAY,CAAA;AAC1B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,OAAO,EACP,QAAQ,EACR,MAAM,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,iBAAiB,EAClB,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,UAAU,EACX,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,SAAS,EACT,SAAS,EACV,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAChE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,cAAc,YAAY,CAAA;AAC1B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,OAAO,EACP,QAAQ,EACR,MAAM,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,iBAAiB,EAClB,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,UAAU,EACX,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,SAAS,EACT,SAAS,EACV,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAChE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAG7D,cAAc,2BAA2B,CAAA;AACzC,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAChF,YAAY,EACV,wBAAwB,EACxB,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,YAAY,EACb,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACd,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,aAAa,EACd,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,QAAQ,EACR,aAAa,EACb,OAAO,EACP,OAAO,EACP,SAAS,EACT,UAAU,EACX,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EACL,aAAa,EACb,eAAe,EACf,gBAAgB,EACjB,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA"}
|