@gravity-ui/gateway 4.7.1 → 4.8.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/README.md CHANGED
@@ -455,17 +455,41 @@ const config = {
455
455
  };
456
456
  ```
457
457
 
458
- You can also define service-specific authentication by adding a `getAuthHeaders` function to individual actions:
458
+ You can define authentication at three levels:
459
+ |
460
+
461
+ 1. **Gateway level** (global) - as shown above
462
+ 2. **Service level** - by adding authentication methods to the service definition
463
+ 3. **Action level** (most specific) - by adding authentication methods to individual actions
464
+ |
465
+ The authentication methods are checked in the following order: action > service > gateway.
466
+ |
467
+ **Service-level authentication:**
468
+ |
459
469
 
460
470
  ```javascript
461
471
  const schema = {
462
472
  userService: {
463
473
  serviceName: 'users',
464
474
  endpoints: {...},
475
+ // Service-level authentication
476
+ getAuthHeaders: (params) => ({
477
+ 'X-User-Service-Auth': params.token,
478
+ }),
479
+ getAuthArgs: (req, res) => ({
480
+ token: req.authorization.token,
481
+ serviceSpecificData: req.headers['x-service-data'],
482
+ }),
465
483
  actions: {
466
484
  getProfile: {
467
485
  path: () => '/profile',
468
486
  method: 'GET',
487
+ // Uses service-level authentication
488
+ },
489
+ updateProfile: {
490
+ path: () => '/profile',
491
+ method: 'PUT',
492
+ // Action-level authentication (overrides service-level)
469
493
  getAuthHeaders: (params) => ({
470
494
  'X-Special-Auth': params.token,
471
495
  }),
@@ -1,7 +1,7 @@
1
1
  import * as grpc from '@grpc/grpc-js';
2
2
  import * as protobufjs from 'protobufjs';
3
3
  import type * as descriptor from 'protobufjs/ext/descriptor';
4
- import { ApiActionConfig, ApiServiceGrpcActionConfig, EndpointsConfig, GatewayApiOptions } from '../models/common';
4
+ import { ApiActionConfig, ApiServiceGrpcActionConfig, BaseSchema, EndpointsConfig, GatewayApiOptions } from '../models/common';
5
5
  import { GatewayContext } from '../models/context';
6
6
  import { AppErrorConstructor } from '../models/error';
7
7
  declare module 'protobufjs' {
@@ -20,5 +20,5 @@ export interface GrpcContext {
20
20
  }
21
21
  export declare function createRoot(includeGrpcPaths?: string[]): protobufjs.Root;
22
22
  export declare function getCredentialsMap(caCertificatePath?: string | null): CredentialsMap;
23
- export default function createGrpcAction<Context extends GatewayContext>({ root, credentials }: GrpcContext, endpoints: EndpointsConfig | undefined, config: ApiServiceGrpcActionConfig<Context, any, any>, serviceKey: string, actionName: string, options: GatewayApiOptions<Context>, ErrorConstructor: AppErrorConstructor): (actionConfig: ApiActionConfig<Context, any, any>) => Promise<import("../models/common").GatewayActionClientStreamResponse<any> | import("../models/common").GatewayActionServerStreamResponse<any> | import("../models/common").GatewayActionDuplexStreamResponse<any> | import("../models/common").GatewayActionUnaryResponse<any>>;
23
+ export default function createGrpcAction<Context extends GatewayContext>({ root, credentials }: GrpcContext, endpoints: EndpointsConfig | undefined, config: ApiServiceGrpcActionConfig<Context, any, any>, serviceKey: string, actionName: string, options: GatewayApiOptions<Context>, ErrorConstructor: AppErrorConstructor, serviceSchema?: Pick<BaseSchema[string], 'getAuthHeaders'>): (actionConfig: ApiActionConfig<Context, any, any>) => Promise<import("../models/common").GatewayActionClientStreamResponse<any> | import("../models/common").GatewayActionServerStreamResponse<any> | import("../models/common").GatewayActionDuplexStreamResponse<any> | import("../models/common").GatewayActionUnaryResponse<any>>;
24
24
  export {};
@@ -86,8 +86,8 @@ function decodeResponse(response, packageRoot, ctx, encodedFields = [], ErrorCon
86
86
  }
87
87
  });
88
88
  }
89
- function createMetadata({ options, actionConfig, config, params, serviceName, proxyHeadersCaller, ctx, }) {
90
- var _a;
89
+ function createMetadata({ options, actionConfig, config, params, serviceName, proxyHeadersCaller, ctx, serviceSchema, }) {
90
+ var _a, _b;
91
91
  const { headers, requestId, authArgs } = actionConfig;
92
92
  const proxyHeaders = [...constants_1.DEFAULT_PROXY_HEADERS];
93
93
  let metadata = {
@@ -111,7 +111,7 @@ function createMetadata({ options, actionConfig, config, params, serviceName, pr
111
111
  metadata[headerName] = headers[headerName];
112
112
  }
113
113
  }
114
- const authHeaders = ((_a = config.getAuthHeaders) !== null && _a !== void 0 ? _a : options.getAuthHeaders)({
114
+ const authHeaders = ((_b = (_a = config.getAuthHeaders) !== null && _a !== void 0 ? _a : serviceSchema === null || serviceSchema === void 0 ? void 0 : serviceSchema.getAuthHeaders) !== null && _b !== void 0 ? _b : options.getAuthHeaders)({
115
115
  actionType: 'grpc',
116
116
  serviceName,
117
117
  requestHeaders: headers,
@@ -327,7 +327,13 @@ async function getResponseData({ config, response, ctx, packageRoot, args, Error
327
327
  }
328
328
  return responseData;
329
329
  }
330
- function createGrpcAction({ root, credentials }, endpoints, config, serviceKey, actionName, options, ErrorConstructor) {
330
+ // Function to generate fresh serviceOptions with updated deadline for each attempt
331
+ function createServiceOptions(timeout) {
332
+ return {
333
+ deadline: Date.now() + timeout,
334
+ };
335
+ }
336
+ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey, actionName, options, ErrorConstructor, serviceSchema) {
331
337
  const serviceName = (options === null || options === void 0 ? void 0 : options.serviceName) || serviceKey;
332
338
  let getService;
333
339
  let recreateService;
@@ -504,9 +510,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
504
510
  });
505
511
  }
506
512
  const timeout = (_c = (_b = (_a = actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.timeout) !== null && _a !== void 0 ? _a : config === null || config === void 0 ? void 0 : config.timeout) !== null && _b !== void 0 ? _b : options === null || options === void 0 ? void 0 : options.timeout) !== null && _c !== void 0 ? _c : constants_1.DEFAULT_TIMEOUT;
507
- const serviceOptions = {
508
- deadline: Date.now() + timeout,
509
- };
513
+ let serviceOptions = createServiceOptions(timeout);
510
514
  const { body = null } = params !== null && params !== void 0 ? params : { body: args };
511
515
  const serviceMetadata = createMetadata({
512
516
  options,
@@ -516,6 +520,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
516
520
  serviceName,
517
521
  proxyHeadersCaller,
518
522
  ctx,
523
+ serviceSchema,
519
524
  });
520
525
  if (!service[action]) {
521
526
  reject(new parse_error_1.GrpcError('Not found action', {
@@ -655,6 +660,8 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
655
660
  stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
656
661
  // Update service
657
662
  actionCall = service[action].bind(service);
663
+ // Update serviceOptions with a fresh deadline for the retry
664
+ serviceOptions = createServiceOptions(timeout);
658
665
  callAction();
659
666
  return;
660
667
  }
@@ -1,7 +1,7 @@
1
- import { ApiActionConfig, ApiServiceRestActionConfig, EndpointsConfig, GatewayApiOptions, Headers } from '../models/common';
1
+ import { ApiActionConfig, ApiServiceRestActionConfig, BaseSchema, EndpointsConfig, GatewayApiOptions, Headers } from '../models/common';
2
2
  import { GatewayContext } from '../models/context';
3
3
  import { AppErrorConstructor } from '../models/error';
4
- export default function createRestAction<Context extends GatewayContext>(endpoints: EndpointsConfig | undefined, config: ApiServiceRestActionConfig<Context, any, any>, serviceKey: string, actionName: string, options: GatewayApiOptions<Context>, ErrorConstructor: AppErrorConstructor): (actionConfig: ApiActionConfig<Context, any>) => Promise<{
4
+ export default function createRestAction<Context extends GatewayContext>(endpoints: EndpointsConfig | undefined, config: ApiServiceRestActionConfig<Context, any, any>, serviceKey: string, actionName: string, options: GatewayApiOptions<Context>, ErrorConstructor: AppErrorConstructor, serviceSchema?: Pick<BaseSchema[string], 'getAuthHeaders'>): (actionConfig: ApiActionConfig<Context, any>) => Promise<{
5
5
  responseData: unknown;
6
6
  responseHeaders?: Headers | undefined;
7
7
  debugHeaders: Headers;
@@ -33,13 +33,13 @@ function getConfigSerializerFunction(config) {
33
33
  }
34
34
  return undefined;
35
35
  }
36
- function createRestAction(endpoints, config, serviceKey, actionName, options, ErrorConstructor) {
36
+ function createRestAction(endpoints, config, serviceKey, actionName, options, ErrorConstructor, serviceSchema) {
37
37
  var _a, _b, _c, _d;
38
38
  const timeout = (_c = (_a = config === null || config === void 0 ? void 0 : config.timeout) !== null && _a !== void 0 ? _a : (_b = options === null || options === void 0 ? void 0 : options.axiosConfig) === null || _b === void 0 ? void 0 : _b.timeout) !== null && _c !== void 0 ? _c : options === null || options === void 0 ? void 0 : options.timeout;
39
39
  const defaultAxiosClient = (0, axios_1.getAxiosClient)(timeout, config === null || config === void 0 ? void 0 : config.retries, (_d = config === null || config === void 0 ? void 0 : config.axiosRetryCondition) !== null && _d !== void 0 ? _d : options === null || options === void 0 ? void 0 : options.axiosRetryCondition, options === null || options === void 0 ? void 0 : options.axiosConfig, options === null || options === void 0 ? void 0 : options.axiosInterceptors);
40
40
  /* eslint-disable complexity */
41
41
  return async function action(actionConfig) {
42
- var _a, _b, _c, _d, _e, _f, _g, _h;
42
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
43
43
  const { args, requestId, headers: requestHeaders, ctx: parentCtx, authArgs, userId, abortSignal, } = actionConfig;
44
44
  const debugHeaders = {};
45
45
  const lang = requestHeaders[constants_1.DEFAULT_LANG_HEADER] || constants_1.Lang.Ru; // header might be empty string
@@ -142,7 +142,7 @@ function createRestAction(endpoints, config, serviceKey, actionName, options, Er
142
142
  if (idempotency) {
143
143
  actionHeaders['idempotency-key'] = requestHeaders['idempotency-key'] || (0, uuid_1.v4)();
144
144
  }
145
- const authHeaders = ((_b = config.getAuthHeaders) !== null && _b !== void 0 ? _b : options.getAuthHeaders)({
145
+ const authHeaders = ((_c = (_b = config.getAuthHeaders) !== null && _b !== void 0 ? _b : serviceSchema === null || serviceSchema === void 0 ? void 0 : serviceSchema.getAuthHeaders) !== null && _c !== void 0 ? _c : options.getAuthHeaders)({
146
146
  actionType: 'rest',
147
147
  serviceName,
148
148
  requestHeaders,
@@ -202,7 +202,7 @@ function createRestAction(endpoints, config, serviceKey, actionName, options, Er
202
202
  }
203
203
  const startRequestTime = Date.now();
204
204
  let axiosClient = defaultAxiosClient;
205
- const customActionTimeout = (_e = (_d = (_c = actionConfig.timeout) !== null && _c !== void 0 ? _c : config.timeout) !== null && _d !== void 0 ? _d : endpointAxiosConfig === null || endpointAxiosConfig === void 0 ? void 0 : endpointAxiosConfig.timeout) !== null && _e !== void 0 ? _e : timeout;
205
+ const customActionTimeout = (_f = (_e = (_d = actionConfig.timeout) !== null && _d !== void 0 ? _d : config.timeout) !== null && _e !== void 0 ? _e : endpointAxiosConfig === null || endpointAxiosConfig === void 0 ? void 0 : endpointAxiosConfig.timeout) !== null && _f !== void 0 ? _f : timeout;
206
206
  if (actionConfig.timeout || endpointAxiosConfig) {
207
207
  const customActionAxiosConfig = Object.assign(Object.assign({}, ((options === null || options === void 0 ? void 0 : options.axiosConfig) || {})), (endpointAxiosConfig || {}));
208
208
  axiosClient = (0, axios_1.getAxiosClient)(customActionTimeout, config === null || config === void 0 ? void 0 : config.retries, options.axiosRetryCondition, customActionAxiosConfig, options === null || options === void 0 ? void 0 : options.axiosInterceptors);
@@ -216,7 +216,7 @@ function createRestAction(endpoints, config, serviceKey, actionName, options, Er
216
216
  requestId,
217
217
  requestMethod: config.method,
218
218
  requestUrl: actionURL,
219
- traceId: ((_f = ctx.getTraceId) === null || _f === void 0 ? void 0 : _f.call(ctx)) || '',
219
+ traceId: ((_g = ctx.getTraceId) === null || _g === void 0 ? void 0 : _g.call(ctx)) || '',
220
220
  userId: userId || '',
221
221
  };
222
222
  const requestConfig = {
@@ -243,7 +243,7 @@ function createRestAction(endpoints, config, serviceKey, actionName, options, Er
243
243
  const responseHeaders = {};
244
244
  const endRequestTime = Date.now();
245
245
  requestData.requestTime = endRequestTime - startRequestTime;
246
- const actualResponseContentType = (_g = response.headers) === null || _g === void 0 ? void 0 : _g['Content-Type'];
246
+ const actualResponseContentType = (_h = response.headers) === null || _h === void 0 ? void 0 : _h['Content-Type'];
247
247
  const expectedResponseContentType = config.expectedResponseContentType || options.expectedResponseContentType;
248
248
  if (actualResponseContentType && expectedResponseContentType) {
249
249
  let isInvalidResponseContentType;
@@ -340,7 +340,7 @@ function createRestAction(endpoints, config, serviceKey, actionName, options, Er
340
340
  }
341
341
  const responseStatus = lodash_1.default.get(parsedError, 'status') || lodash_1.default.get(error, 'status', 500);
342
342
  if (options === null || options === void 0 ? void 0 : options.sendStats) {
343
- options.sendStats(Object.assign(Object.assign({}, requestData), { responseSize: getRestResponseSize((_h = error === null || error === void 0 ? void 0 : error.response) === null || _h === void 0 ? void 0 : _h.data, ctx, ErrorConstructor), restStatus: responseStatus, userId }), (0, redact_sensitive_headers_1.redactSensitiveHeaders)(parentCtx, headers), parentCtx, { debugHeaders: (0, common_1.sanitizeDebugHeaders)(debugHeaders) });
343
+ options.sendStats(Object.assign(Object.assign({}, requestData), { responseSize: getRestResponseSize((_j = error === null || error === void 0 ? void 0 : error.response) === null || _j === void 0 ? void 0 : _j.data, ctx, ErrorConstructor), restStatus: responseStatus, userId }), (0, redact_sensitive_headers_1.redactSensitiveHeaders)(parentCtx, headers), parentCtx, { debugHeaders: (0, common_1.sanitizeDebugHeaders)(debugHeaders) });
344
344
  }
345
345
  else {
346
346
  ctx.stats(Object.assign(Object.assign({}, requestData), { responseStatus }));
package/build/index.js CHANGED
@@ -85,7 +85,7 @@ function createApiAction(schema, config, serviceKey, actionName, api, grpcContex
85
85
  validationSchema: config.validationSchema,
86
86
  encodePathArgs: config.encodePathArgs,
87
87
  getAuthHeaders: config.getAuthHeaders,
88
- }, config.ErrorConstructor);
88
+ }, config.ErrorConstructor, serviceSchema);
89
89
  }
90
90
  const grpcRecreateService = (_a = config.grpcRecreateService) !== null && _a !== void 0 ? _a : true;
91
91
  return (0, grpc_1.default)(grpcContext, endpointsConfig, action, serviceKey, actionName, {
@@ -98,7 +98,7 @@ function createApiAction(schema, config, serviceKey, actionName, api, grpcContex
98
98
  grpcOptions: config.grpcOptions,
99
99
  grpcRecreateService,
100
100
  getAuthHeaders: config.getAuthHeaders,
101
- }, config.ErrorConstructor);
101
+ }, config.ErrorConstructor, serviceSchema);
102
102
  }
103
103
  function generateGatewayApi(schema, config, grpcContext, baseApi) {
104
104
  const { installation, env } = config;
@@ -117,7 +117,7 @@ function generateGatewayApi(schema, config, grpcContext, baseApi) {
117
117
  function generateGatewayApiController(schemasByScope, Api, config, controllerActions) {
118
118
  // eslint-disable-next-line complexity
119
119
  return async function gateway(req, res) {
120
- var _a, _b, _c;
120
+ var _a, _b, _c, _d;
121
121
  const { userId } = res.locals || {};
122
122
  const { service, action, scope = 'root' } = req.params;
123
123
  const withDebugHeaders = typeof config.withDebugHeaders === 'function'
@@ -192,12 +192,15 @@ function generateGatewayApiController(schemasByScope, Api, config, controllerAct
192
192
  throw { error, debugHeaders: {} };
193
193
  }
194
194
  }
195
+ const serviceSchema = (_d = schemasByScope[scope]) === null || _d === void 0 ? void 0 : _d[service];
195
196
  const { responseData, responseHeaders, debugHeaders } = await apiAction({
196
197
  requestId: req.id,
197
198
  headers: req.headers,
198
199
  ctx: req.ctx,
199
200
  args,
200
- authArgs: config.getAuthArgs(req, res),
201
+ authArgs: (serviceSchema === null || serviceSchema === void 0 ? void 0 : serviceSchema.getAuthArgs)
202
+ ? serviceSchema.getAuthArgs(req, res)
203
+ : config.getAuthArgs(req, res),
201
204
  userId,
202
205
  abortSignal: abortController.signal,
203
206
  });
@@ -199,6 +199,8 @@ export interface BaseSchema {
199
199
  actions: Record<string, ApiServiceActionConfig<any, any, any, any, any, any>>;
200
200
  serviceName?: string;
201
201
  endpoints?: Record<string, Record<string, EndpointsConfig>>;
202
+ getAuthHeaders?: GetAuthHeaders;
203
+ getAuthArgs?: (req: Request, res: Response) => Record<string, unknown> | undefined;
202
204
  };
203
205
  }
204
206
  export interface SchemasByScope {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/gateway",
3
- "version": "4.7.1",
3
+ "version": "4.8.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",