@axinom/mosaic-id-guard 0.18.0-rc.8 → 0.18.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.
Files changed (49) hide show
  1. package/dist/common/helpers/guard-authorization.d.ts +22 -0
  2. package/dist/common/helpers/guard-authorization.d.ts.map +1 -0
  3. package/dist/common/helpers/guard-authorization.js +49 -0
  4. package/dist/common/helpers/guard-authorization.js.map +1 -0
  5. package/dist/common/id-guard-error.d.ts +1 -0
  6. package/dist/common/id-guard-error.d.ts.map +1 -1
  7. package/dist/common/id-guard-error.js +7 -1
  8. package/dist/common/id-guard-error.js.map +1 -1
  9. package/dist/common/id-guard-errors.d.ts +8 -0
  10. package/dist/common/id-guard-errors.d.ts.map +1 -1
  11. package/dist/common/id-guard-errors.js +8 -0
  12. package/dist/common/id-guard-errors.js.map +1 -1
  13. package/dist/graphql/ax-guard-plugin.d.ts +23 -0
  14. package/dist/graphql/ax-guard-plugin.d.ts.map +1 -0
  15. package/dist/graphql/ax-guard-plugin.js +29 -0
  16. package/dist/graphql/ax-guard-plugin.js.map +1 -0
  17. package/dist/graphql/enforce-strict-permissions.plugin.d.ts +1 -1
  18. package/dist/graphql/enforce-strict-permissions.plugin.d.ts.map +1 -1
  19. package/dist/graphql/index.d.ts +3 -2
  20. package/dist/graphql/index.d.ts.map +1 -1
  21. package/dist/graphql/index.js +3 -2
  22. package/dist/graphql/index.js.map +1 -1
  23. package/dist/graphql/{guard-plugin.d.ts → query-mutation-guard-plugin.d.ts} +2 -2
  24. package/dist/graphql/query-mutation-guard-plugin.d.ts.map +1 -0
  25. package/dist/graphql/{guard-plugin.js → query-mutation-guard-plugin.js} +5 -26
  26. package/dist/graphql/query-mutation-guard-plugin.js.map +1 -0
  27. package/dist/graphql/subscription-guard-plugin.d.ts +20 -0
  28. package/dist/graphql/subscription-guard-plugin.d.ts.map +1 -0
  29. package/dist/graphql/subscription-guard-plugin.js +81 -0
  30. package/dist/graphql/subscription-guard-plugin.js.map +1 -0
  31. package/package.json +9 -10
  32. package/src/common/helpers/guard-authorization.ts +76 -0
  33. package/src/common/id-guard-error.ts +13 -0
  34. package/src/common/id-guard-errors.ts +10 -0
  35. package/src/graphql/ax-guard-plugin.ts +29 -0
  36. package/src/graphql/enforce-strict-permissions.plugin.ts +1 -1
  37. package/src/graphql/index.ts +3 -2
  38. package/src/graphql/{guard-plugin.spec.ts → query-mutation-guard-plugin.spec.ts} +3 -3
  39. package/src/graphql/{guard-plugin.ts → query-mutation-guard-plugin.ts} +12 -36
  40. package/src/graphql/subscription-guard-plugin.spec.ts +257 -0
  41. package/src/graphql/subscription-guard-plugin.ts +112 -0
  42. package/dist/graphql/guard-plugin.d.ts.map +0 -1
  43. package/dist/graphql/guard-plugin.js.map +0 -1
  44. package/dist/graphql/subscription-authorization-hook-factory.d.ts +0 -13
  45. package/dist/graphql/subscription-authorization-hook-factory.d.ts.map +0 -1
  46. package/dist/graphql/subscription-authorization-hook-factory.js +0 -182
  47. package/dist/graphql/subscription-authorization-hook-factory.js.map +0 -1
  48. package/src/graphql/subscription-authorization-hook-factory.spec.ts +0 -749
  49. package/src/graphql/subscription-authorization-hook-factory.ts +0 -286
@@ -1,286 +0,0 @@
1
- import {
2
- EndUserAuthorizationConfig,
3
- PermissionDefinition,
4
- } from '@axinom/mosaic-id-utils';
5
- import { MosaicErrorInfo, NonMosaicError } from '@axinom/mosaic-service-common';
6
- import { Request, Response } from 'express';
7
- import {
8
- DocumentNode,
9
- ExecutionArgs,
10
- FieldNode,
11
- OperationDefinitionNode,
12
- } from 'graphql';
13
- import gql from 'graphql-tag';
14
- import { CloseCode, Context, SubscribeMessage } from 'graphql-ws';
15
- import { Extra } from 'graphql-ws/lib/use/ws';
16
- import {
17
- CreateRequestHandlerOptions,
18
- PostGraphileOptions,
19
- PostGraphilePlugin,
20
- } from 'postgraphile';
21
- import { ExecutionParams } from 'subscriptions-transport-ws';
22
- import {
23
- assertGenericAuthenticatedSubject,
24
- IdGuardError,
25
- IdGuardErrors,
26
- } from '../common';
27
- import {
28
- GqlAuthorizationOptions,
29
- isAuthenticatedEndUser,
30
- isAuthenticatedManagementSubject,
31
- } from '../common/guard-utils';
32
- import { handleEndUserAuthorization } from '../common/handle-end-user-authorization';
33
- import { handleManagementUserAuthorization } from '../common/handle-management-user-authorization';
34
- import { AuthenticatedRequest } from '../common/types/authentication-request-context';
35
-
36
- /**
37
- * This is a hook plugin generator function that will use the PermissionDefinition object in PostGraphileOptions
38
- * to authorize the Subscription operation done over a web socket.
39
- * It checks if the authenticated user which initiates the subscription has required permissions.
40
- * The actual plugin is built using this function when the build() function is called in PostgraphileOptionsBuilder.
41
- * This function reference can be passed into addHookPluginGenerator() to activate authorization of subscriptions.
42
- * @param PostGraphileOptions object containing permissions.
43
- * @returns PostGraphilePlugin
44
- */
45
- export const subscriptionAuthorizationHookFactory = (
46
- buildOptions: PostGraphileOptions<Request, Response> | undefined,
47
- ): PostGraphilePlugin => {
48
- return {
49
- // Handles subscription requests coming through protocol `graphql-ws`
50
- // This is maintained for backwards compatibility. Managed services use `graphql-transport-ws` protocol (implemented below)
51
- 'postgraphile:ws:onOperation': (params: ExecutionParams) => {
52
- // Extract GQL query and build an AST
53
- // Need to determine the Subscription that's being called.
54
- const gqlQuery = gql(params.query.toString());
55
- const operationDefinitions =
56
- gqlQuery.definitions as OperationDefinitionNode[];
57
-
58
- const subscription = getSubscription(
59
- operationDefinitions,
60
- params.operationName,
61
- );
62
-
63
- if (
64
- buildOptions !== undefined &&
65
- buildOptions.graphileBuildOptions !== undefined &&
66
- subscription !== undefined
67
- ) {
68
- // Get permissionDefinition from graphileBuildOptions
69
- const permissionDefinition = buildOptions?.graphileBuildOptions
70
- .permissionDefinition as PermissionDefinition;
71
-
72
- const endUserAuthorizationConfig = buildOptions?.graphileBuildOptions
73
- .endUserAuthorizationConfig as EndUserAuthorizationConfig;
74
-
75
- const ensureOnlyAuthentication = buildOptions?.graphileBuildOptions
76
- .ensureOnlyAuthentication as boolean;
77
-
78
- const options: GqlAuthorizationOptions = {
79
- operation: subscription.name.value, // the subscription name extracted from subscription operation.
80
- permissionDefinition,
81
- serviceId: params.context.config.serviceId,
82
- endUserAuthorizationConfig,
83
- };
84
- const error: MosaicErrorInfo | undefined = undefined;
85
-
86
- const subject = params.context.subject;
87
- assertGenericAuthenticatedSubject(subject);
88
-
89
- // If the authorization fails, throw error
90
- if (isAuthenticatedManagementSubject(subject)) {
91
- handleManagementUserAuthorization(
92
- options,
93
- ensureOnlyAuthentication,
94
- subject,
95
- error,
96
- );
97
- return params;
98
- } else if (isAuthenticatedEndUser(subject)) {
99
- handleEndUserAuthorization(
100
- options,
101
- ensureOnlyAuthentication,
102
- subject,
103
- error,
104
- );
105
- return params;
106
- } else {
107
- throw new IdGuardError({
108
- message: 'User is not authorized for this subscription.',
109
- code: IdGuardErrors.UserNotAuthorized.code,
110
- details: {
111
- user: params.context.subject?.sub,
112
- serviceId: options.serviceId,
113
- },
114
- });
115
- }
116
- }
117
- throw new Error(
118
- 'Error in subscription setup. This is likely caused by a development time issue.',
119
- );
120
- },
121
-
122
- // Handles subscription requests coming through protocol `graphql-transport-ws`
123
- // Managed services use this protocol for subscriptions.
124
- 'postgraphile:ws:onSubscribe': (
125
- args: ExecutionArgs & {
126
- document: DocumentNode | null;
127
- },
128
- params: {
129
- context: Context<Extra>;
130
- message: SubscribeMessage;
131
- options: CreateRequestHandlerOptions;
132
- },
133
- ) => {
134
- // If there were any Authentication Errors, close the web socket.
135
- if (
136
- (params.context.extra.request as AuthenticatedRequest).authContext
137
- .authErrorInfo !== undefined
138
- ) {
139
- params.context.extra.socket.close(
140
- CloseCode.Forbidden,
141
- IdGuardErrors.AccessTokenInvalid.code,
142
- );
143
- return args;
144
- }
145
-
146
- const definitionNodes = args.document
147
- .definitions as OperationDefinitionNode[];
148
-
149
- const subscription = getSubscription(
150
- definitionNodes,
151
- args.operationName ?? undefined,
152
- );
153
- if (
154
- buildOptions !== undefined &&
155
- buildOptions.graphileBuildOptions !== undefined &&
156
- subscription !== undefined
157
- ) {
158
- // Get permissionDefinition from graphileBuildOptions
159
- const permissionDefinition = buildOptions?.graphileBuildOptions
160
- .permissionDefinition as PermissionDefinition;
161
-
162
- const endUserAuthorizationConfig = buildOptions?.graphileBuildOptions
163
- .endUserAuthorizationConfig as EndUserAuthorizationConfig;
164
-
165
- const ensureOnlyAuthentication = buildOptions?.graphileBuildOptions
166
- .ensureOnlyAuthentication as boolean;
167
-
168
- const options: GqlAuthorizationOptions = {
169
- operation: subscription.name.value, // the subscription name extracted from subscription operation.
170
- permissionDefinition,
171
- serviceId: args.contextValue.config.serviceId,
172
- endUserAuthorizationConfig,
173
- };
174
- const error: MosaicErrorInfo | undefined = undefined;
175
-
176
- const subject = args.contextValue.subject;
177
- assertGenericAuthenticatedSubject(subject);
178
-
179
- // If the authorization fails, throw error
180
- // Authorization is only checked for Management tokens
181
- if (isAuthenticatedManagementSubject(subject)) {
182
- try {
183
- handleManagementUserAuthorization(
184
- options,
185
- ensureOnlyAuthentication,
186
- subject,
187
- error,
188
- );
189
- } catch (error) {
190
- assertIdGuardError(error);
191
- /**
192
- * If the token has expired, we close the connection with CloseCode.Forbidden, and allow the client to automatically re-establish
193
- * the connection with a new token.
194
- */
195
- if (error.code === IdGuardErrors.AccessTokenExpired.code) {
196
- /**
197
- * `CloseCode.Forbidden` is used so that the client will automatically start retry process with a newer token.
198
- * Any other CloseCode will result in a client exception and the client will not automatically retry.
199
- */
200
- params.context.extra.socket.close(
201
- CloseCode.Forbidden,
202
- IdGuardErrors.AccessTokenExpired.code,
203
- );
204
- } else {
205
- throw error;
206
- }
207
- }
208
- return args;
209
- } else if (isAuthenticatedEndUser(subject)) {
210
- try {
211
- handleEndUserAuthorization(
212
- options,
213
- ensureOnlyAuthentication,
214
- subject,
215
- error,
216
- );
217
- } catch (error) {
218
- assertIdGuardError(error);
219
- /**
220
- * If the token has expired, we close the connection with CloseCode.Forbidden, and allow the client to automatically re-establish
221
- * the connection with a new token.
222
- */
223
- if (error.code === IdGuardErrors.AccessTokenExpired.code) {
224
- /**
225
- * `CloseCode.Forbidden` is used so that the client will automatically start retry process with a newer token.
226
- * Any other CloseCode will result in a client exception and the client will not automatically retry.
227
- */
228
- params.context.extra.socket.close(
229
- CloseCode.Forbidden,
230
- IdGuardErrors.AccessTokenExpired.code,
231
- );
232
- } else {
233
- throw error;
234
- }
235
- }
236
- return args;
237
- }
238
- }
239
- /**
240
- * `graphql-transport-ws` does not propagate thrown exceptions to the client.
241
- * So if either `graphileBuildOptions` or `subscription` is undefined, we close the web socket connection with BadRequest error.
242
- * The client will have to explicitly re-establish the web socket connection.
243
- */
244
- params.context.extra.socket.close(
245
- CloseCode.BadRequest,
246
- 'Error in subscription setup. This is likely caused by a development time issue.',
247
- );
248
- return args;
249
- },
250
- };
251
- };
252
-
253
- /**
254
- * Extract Subscription from the AST.
255
- */
256
- const getSubscription = (
257
- operationDefinitions: OperationDefinitionNode[],
258
- operationName: string | undefined,
259
- ): FieldNode | undefined => {
260
- // In a subscription request query, there can be only one subscription,
261
- // and only one operation with the same name.
262
- // We extract the AST for the Subscription using the operationName sent in params
263
- const subscriptionDefinition = operationDefinitions.filter(
264
- (opDefs) => opDefs.name?.value === operationName,
265
- )[0];
266
-
267
- if (subscriptionDefinition !== undefined) {
268
- // If the called operation is found in the query,
269
- // proceed to extract the subscription from the GQL Operation definition.
270
- // There can only be one subscription in a Subscription operation, hence getting the [0]th element.
271
- const subscription = (
272
- subscriptionDefinition.selectionSet.selections as FieldNode[]
273
- )[0];
274
- return subscription;
275
- }
276
- };
277
-
278
- const assertIdGuardError: (error: unknown) => asserts error is IdGuardError = (
279
- error: unknown,
280
- ): asserts error is IdGuardError => {
281
- if (!(error instanceof IdGuardError)) {
282
- throw new NonMosaicError(
283
- `A caught error is not an instance of an IdGuardError class.`,
284
- );
285
- }
286
- };