@axinom/mosaic-id-guard 0.47.0-rc.8 → 0.47.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axinom/mosaic-id-guard",
3
- "version": "0.47.0-rc.8",
3
+ "version": "0.47.0",
4
4
  "description": "Authentication and authorization helpers for Axinom Mosaic services",
5
5
  "author": "Axinom",
6
6
  "license": "PROPRIETARY",
@@ -30,10 +30,10 @@
30
30
  "lint": "eslint . --ext .ts,.tsx,.js --color --cache"
31
31
  },
32
32
  "dependencies": {
33
- "@axinom/mosaic-id-utils": "^0.26.0-rc.8",
34
- "@axinom/mosaic-message-bus": "^0.49.0-rc.8",
35
- "@axinom/mosaic-service-common": "^0.68.0-rc.8",
36
- "@axinom/mosaic-transactional-inbox-outbox": "^0.28.0-rc.8",
33
+ "@axinom/mosaic-id-utils": "^0.26.0",
34
+ "@axinom/mosaic-message-bus": "^0.49.0",
35
+ "@axinom/mosaic-service-common": "^0.68.0",
36
+ "@axinom/mosaic-transactional-inbox-outbox": "^0.28.0",
37
37
  "amqplib": "^0.10.3",
38
38
  "express": "^4.17.1",
39
39
  "express-bearer-token": "^3.0.0",
@@ -69,5 +69,5 @@
69
69
  "publishConfig": {
70
70
  "access": "public"
71
71
  },
72
- "gitHead": "99551cfc139298ca69912ec7a678e1c1092ff1f3"
72
+ "gitHead": "9732fd094fafedb5360d10405cc19fa202d42de6"
73
73
  }
@@ -2,6 +2,7 @@ import { skipMaskTag } from '@axinom/mosaic-service-common';
2
2
  import {
3
3
  MutationPayloadQueryPlugin,
4
4
  MutationPlugin,
5
+ Plugin,
5
6
  QueryPlugin,
6
7
  StandardTypesPlugin,
7
8
  SubscriptionPlugin,
@@ -989,3 +990,342 @@ describe('QueryMutationGuardPlugin', () => {
989
990
  });
990
991
  });
991
992
  });
993
+
994
+ describe('When an operation is renamed via a hook plugin', () => {
995
+ const RenameEchoPlugin: Plugin = (builder) => {
996
+ builder.hook('GraphQLObjectType:fields', (fields, _build, { Self }) => {
997
+ if (Self.name === 'Query' && 'echo' in fields) {
998
+ const { echo, ...rest } = fields;
999
+ return { ...rest, renamedEcho: echo };
1000
+ }
1001
+ if (Self.name === 'Mutation' && 'echoMutation' in fields) {
1002
+ const { echoMutation, ...rest } = fields;
1003
+ return { ...rest, renamedEchoMutation: echoMutation };
1004
+ }
1005
+ if (Self.name === 'Subscription' && 'echoSubscription' in fields) {
1006
+ const { echoSubscription, ...rest } = fields;
1007
+ return { ...rest, renamedEchoSubscription: echoSubscription };
1008
+ }
1009
+ return fields;
1010
+ });
1011
+ };
1012
+
1013
+ const makeSchemaWithRenamePlugin = async (
1014
+ spy: any,
1015
+ options = {},
1016
+ ): Promise<GraphQLSchema> =>
1017
+ buildSchema(
1018
+ [
1019
+ StandardTypesPlugin,
1020
+ QueryPlugin,
1021
+ MutationPlugin,
1022
+ MutationPayloadQueryPlugin,
1023
+ SubscriptionPlugin,
1024
+ makeExtendSchemaPlugin(() => ({
1025
+ typeDefs: gql`
1026
+ extend type Query {
1027
+ echo(message: String!): String
1028
+ }
1029
+ extend type Mutation {
1030
+ echoMutation(message: String!): String
1031
+ }
1032
+ extend type Subscription {
1033
+ echoSubscription: String
1034
+ }
1035
+ `,
1036
+ resolvers: {
1037
+ Query: {
1038
+ echo: spy,
1039
+ },
1040
+ Mutation: {
1041
+ echoMutation: spy,
1042
+ },
1043
+ Subscription: {
1044
+ echoSubscription: {
1045
+ subscribe: () => pubSub.asyncIterator('echoSubscription'),
1046
+ resolve: () => 'Hello Subscription',
1047
+ },
1048
+ },
1049
+ },
1050
+ })),
1051
+ RenameEchoPlugin,
1052
+ QueryMutationGuardPlugin,
1053
+ ],
1054
+ {
1055
+ serviceId: 'test-service',
1056
+ ...options,
1057
+ },
1058
+ );
1059
+
1060
+ it('Authorized user can access the renamed query', async () => {
1061
+ // Arrange
1062
+ const spy = makeEchoSpy();
1063
+ const schema = await makeSchemaWithRenamePlugin(spy, {
1064
+ permissionDefinition: {
1065
+ gqlOptions: {
1066
+ anonymousGqlOperations: [],
1067
+ ignoredGqlOperations: [],
1068
+ },
1069
+ permissions: [
1070
+ {
1071
+ key: 'Editor',
1072
+ title: 'Editor',
1073
+ gqlOperations: ['renamedEcho'],
1074
+ },
1075
+ ],
1076
+ },
1077
+ });
1078
+ const jwtContext = {
1079
+ subject: {
1080
+ sub: 'test-user',
1081
+ permissions: { 'test-service': ['Editor'] },
1082
+ subjectType: SubjectType.UserAccount,
1083
+ },
1084
+ };
1085
+
1086
+ // Act
1087
+ const result = await graphql(
1088
+ schema,
1089
+ '{ renamedEcho(message: "Hello") }',
1090
+ null,
1091
+ jwtContext,
1092
+ );
1093
+
1094
+ // Assert
1095
+ expect(result.errors).toBeUndefined();
1096
+ expect(spy).toHaveBeenCalledTimes(1);
1097
+ expect(result.data?.renamedEcho).toBe('Hello');
1098
+ });
1099
+
1100
+ it('Unauthorized user cannot access the renamed query', async () => {
1101
+ // Arrange
1102
+ const spy = makeEchoSpy();
1103
+ const schema = await makeSchemaWithRenamePlugin(spy, {
1104
+ permissionDefinition: {
1105
+ gqlOptions: {
1106
+ anonymousGqlOperations: [],
1107
+ ignoredGqlOperations: [],
1108
+ },
1109
+ permissions: [
1110
+ {
1111
+ key: 'Editor',
1112
+ title: 'Editor',
1113
+ gqlOperations: ['renamedEcho'],
1114
+ },
1115
+ ],
1116
+ },
1117
+ });
1118
+ const jwtContext = {
1119
+ subject: {
1120
+ sub: 'test-user',
1121
+ permissions: { 'test-service': ['Viewer'] },
1122
+ subjectType: SubjectType.UserAccount,
1123
+ },
1124
+ };
1125
+
1126
+ // Act
1127
+ const result = await graphql(
1128
+ schema,
1129
+ '{ renamedEcho(message: "Hello") }',
1130
+ null,
1131
+ jwtContext,
1132
+ );
1133
+
1134
+ // Assert
1135
+ expect(result.data?.renamedEcho).toBeNull();
1136
+ expect(spy).toHaveBeenCalledTimes(0);
1137
+ expect(result.errors).toHaveLength(1);
1138
+ const error = result.errors![0].originalError as any;
1139
+ expect(error.code).toBe(IdGuardErrors.UserNotAuthorized.code);
1140
+ });
1141
+
1142
+ it('Authorized user can access the renamed mutation', async () => {
1143
+ // Arrange
1144
+ const spy = makeEchoSpy();
1145
+ const schema = await makeSchemaWithRenamePlugin(spy, {
1146
+ permissionDefinition: {
1147
+ gqlOptions: {
1148
+ anonymousGqlOperations: [],
1149
+ ignoredGqlOperations: [],
1150
+ },
1151
+ permissions: [
1152
+ {
1153
+ key: 'Editor',
1154
+ title: 'Editor',
1155
+ gqlOperations: ['renamedEchoMutation'],
1156
+ },
1157
+ ],
1158
+ },
1159
+ });
1160
+ const jwtContext = {
1161
+ subject: {
1162
+ sub: 'test-user',
1163
+ permissions: { 'test-service': ['Editor'] },
1164
+ subjectType: SubjectType.UserAccount,
1165
+ },
1166
+ };
1167
+
1168
+ // Act
1169
+ const result = await graphql(
1170
+ schema,
1171
+ 'mutation { renamedEchoMutation(message: "Hello") }',
1172
+ null,
1173
+ jwtContext,
1174
+ );
1175
+
1176
+ // Assert
1177
+ expect(result.errors).toBeUndefined();
1178
+ expect(spy).toHaveBeenCalledTimes(1);
1179
+ expect(result.data?.renamedEchoMutation).toBe('Hello');
1180
+ });
1181
+
1182
+ it('Unauthorized user cannot access the renamed mutation', async () => {
1183
+ // Arrange
1184
+ const spy = makeEchoSpy();
1185
+ const schema = await makeSchemaWithRenamePlugin(spy, {
1186
+ permissionDefinition: {
1187
+ gqlOptions: {
1188
+ anonymousGqlOperations: [],
1189
+ ignoredGqlOperations: [],
1190
+ },
1191
+ permissions: [
1192
+ {
1193
+ key: 'Editor',
1194
+ title: 'Editor',
1195
+ gqlOperations: ['renamedEchoMutation'],
1196
+ },
1197
+ ],
1198
+ },
1199
+ });
1200
+ const jwtContext = {
1201
+ subject: {
1202
+ sub: 'test-user',
1203
+ permissions: { 'test-service': ['Viewer'] },
1204
+ subjectType: SubjectType.UserAccount,
1205
+ },
1206
+ };
1207
+
1208
+ // Act
1209
+ const result = await graphql(
1210
+ schema,
1211
+ 'mutation { renamedEchoMutation(message: "Hello") }',
1212
+ null,
1213
+ jwtContext,
1214
+ );
1215
+
1216
+ // Assert
1217
+ expect(result.data?.renamedEchoMutation).toBeNull();
1218
+ expect(spy).toHaveBeenCalledTimes(0);
1219
+ expect(result.errors).toHaveLength(1);
1220
+ const error = result.errors![0].originalError as any;
1221
+ expect(error.code).toBe(IdGuardErrors.UserNotAuthorized.code);
1222
+ });
1223
+
1224
+ it('Authorized user can access the renamed subscription', async () => {
1225
+ // Arrange
1226
+ const spy = makeEchoSpy();
1227
+ const schema = await makeSchemaWithRenamePlugin(spy, {
1228
+ permissionDefinition: {
1229
+ gqlOptions: {
1230
+ anonymousGqlOperations: [],
1231
+ ignoredGqlOperations: [],
1232
+ },
1233
+ permissions: [
1234
+ {
1235
+ key: 'Editor',
1236
+ title: 'Editor',
1237
+ gqlOperations: ['renamedEchoSubscription'],
1238
+ },
1239
+ ],
1240
+ },
1241
+ });
1242
+ const jwtContext = {
1243
+ subject: {
1244
+ sub: 'test-user',
1245
+ permissions: { 'test-service': ['Editor'] },
1246
+ exp: new Date().getTime() / 1000 + 20,
1247
+ subjectType: SubjectType.UserAccount,
1248
+ },
1249
+ };
1250
+
1251
+ const document = parse('subscription { renamedEchoSubscription }');
1252
+
1253
+ // Create subscription
1254
+ const subscription: any = await subscribe({
1255
+ schema,
1256
+ document,
1257
+ rootValue: null,
1258
+ contextValue: jwtContext,
1259
+ });
1260
+
1261
+ const resolveSubscription = subscription.next();
1262
+
1263
+ // Publish trigger to invoke subscription resolver
1264
+ pubSub.publish('echoSubscription', 'message');
1265
+
1266
+ // Await for the subscription resolver to respond
1267
+ const result = await resolveSubscription;
1268
+
1269
+ expect(result.value.errors).toBeFalsy();
1270
+ expect(result.value.data.renamedEchoSubscription).toBe(
1271
+ 'Hello Subscription',
1272
+ );
1273
+
1274
+ // Close subscription
1275
+ await subscription.return();
1276
+ });
1277
+
1278
+ it('Unauthorized user cannot access the renamed subscription', async () => {
1279
+ // Arrange
1280
+ const spy = makeEchoSpy();
1281
+ const schema = await makeSchemaWithRenamePlugin(spy, {
1282
+ permissionDefinition: {
1283
+ gqlOptions: {
1284
+ anonymousGqlOperations: [],
1285
+ ignoredGqlOperations: [],
1286
+ },
1287
+ permissions: [
1288
+ {
1289
+ key: 'Editor',
1290
+ title: 'Editor',
1291
+ gqlOperations: ['renamedEchoSubscription'],
1292
+ },
1293
+ ],
1294
+ },
1295
+ });
1296
+ const jwtContext = {
1297
+ subject: {
1298
+ sub: 'test-user',
1299
+ permissions: { 'test-service': ['Viewer'] },
1300
+ exp: new Date().getTime() / 1000 + 20,
1301
+ subjectType: SubjectType.UserAccount,
1302
+ },
1303
+ };
1304
+
1305
+ const document = parse('subscription { renamedEchoSubscription }');
1306
+
1307
+ // Create subscription
1308
+ const subscription: any = await subscribe({
1309
+ schema,
1310
+ document,
1311
+ rootValue: null,
1312
+ contextValue: jwtContext,
1313
+ });
1314
+
1315
+ const resolveSubscription = subscription.next();
1316
+
1317
+ // Publish trigger to invoke subscription resolver
1318
+ pubSub.publish('echoSubscription', 'message');
1319
+
1320
+ // Await for the subscription resolver to respond
1321
+ const result = await resolveSubscription;
1322
+
1323
+ expect(result.value.errors).toBeTruthy();
1324
+ expect(result.value.errors[0].message).toBe(
1325
+ 'User is not authorized to access the operation.',
1326
+ );
1327
+
1328
+ // Close subscription
1329
+ await subscription.return();
1330
+ });
1331
+ });