From b15393327052dd02ef5ff3aa1bec5a9d6c1e7e6b Mon Sep 17 00:00:00 2001 From: Benny Thomas Date: Thu, 19 Mar 2026 13:43:13 +0100 Subject: [PATCH] fix(apiGateway): support object form for apiKeys entries Resolves #471. The Serverless Framework allows apiKeys entries to be objects with name/value properties, but this plugin threw "API Keys must be strings" for any non-string entry. Now accepts objects with at least a name property and maps value to the CloudFormation Value property when provided. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 4 +++ lib/deploy/events/apiGateway/apiKeys.js | 29 +++++++++++------ lib/deploy/events/apiGateway/apiKeys.test.js | 33 +++++++++++++++++++- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0a8f2fa8..017ba10a 100644 --- a/README.md +++ b/README.md @@ -968,6 +968,8 @@ provider: - myFirstKey - ${opt:stage}-myFirstKey - ${env:MY_API_KEY} # you can hide it in a serverless variable + - name: myKeyWithValue # object form — lets you set a specific key value + value: myApiKeyValue usagePlan: quota: limit: 5000 @@ -1004,6 +1006,8 @@ functions: - serverless-step-functions ``` +API key entries can be plain strings (the key name) or objects with a `name` and an optional `value` property. When `value` is provided, the API key is created with that specific value; otherwise AWS auto-generates the value. + Please note that those are the API keys names, not the actual values. Once you deploy your service, the value of those API keys will be auto generated by AWS and printed on the screen for you to use. The values can be concealed from the output with the --conceal deploy option. Clients connecting to this Rest API will then need to set any of these API keys values in the x-api-key header of their request. This is only necessary for functions where the private property is set to true. diff --git a/lib/deploy/events/apiGateway/apiKeys.js b/lib/deploy/events/apiGateway/apiKeys.js index feda16d7..74ea4132 100644 --- a/lib/deploy/events/apiGateway/apiKeys.js +++ b/lib/deploy/events/apiGateway/apiKeys.js @@ -15,24 +15,33 @@ module.exports = { _.forEach(apiKeys, (apiKey, i) => { const apiKeyNumber = i + 1; - if (typeof apiKey !== 'string') { - throw new this.serverless.classes.Error('API Keys must be strings'); + if (typeof apiKey !== 'string' && (typeof apiKey !== 'object' || !apiKey.name)) { + throw new this.serverless.classes.Error('API Keys must be strings or objects with a name property'); } + const apiKeyName = typeof apiKey === 'object' ? apiKey.name : apiKey; + const apiKeyValue = typeof apiKey === 'object' ? apiKey.value : undefined; + const apiKeyLogicalId = this.provider.naming .getApiKeyLogicalId(apiKeyNumber); + const properties = { + Enabled: true, + Name: apiKeyName, + StageKeys: [{ + RestApiId: { Ref: this.apiGatewayRestApiLogicalId }, + StageName: this.provider.getStage(), + }], + }; + + if (apiKeyValue !== undefined) { + properties.Value = apiKeyValue; + } + _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [apiKeyLogicalId]: { Type: 'AWS::ApiGateway::ApiKey', - Properties: { - Enabled: true, - Name: apiKey, - StageKeys: [{ - RestApiId: { Ref: this.apiGatewayRestApiLogicalId }, - StageName: this.provider.getStage(), - }], - }, + Properties: properties, DependsOn: this.apiGatewayDeploymentLogicalId, }, }); diff --git a/lib/deploy/events/apiGateway/apiKeys.test.js b/lib/deploy/events/apiGateway/apiKeys.test.js index da778587..3b975746 100644 --- a/lib/deploy/events/apiGateway/apiKeys.test.js +++ b/lib/deploy/events/apiGateway/apiKeys.test.js @@ -84,8 +84,39 @@ describe('#methods()', () => { expect(() => serverlessStepFunctions.compileApiKeys()).to.throw(Error); }); - it('throw error if an apiKey is not a string', () => { + it('throw error if an apiKey is not a string or object', () => { serverlessStepFunctions.serverless.service.provider.apiGateway.apiKeys = [2]; expect(() => serverlessStepFunctions.compileApiKeys()).to.throw(Error); }); + + it('should compile api key resource when apiKey is an object with name and value', () => { + serverlessStepFunctions.serverless.service.provider.apiGateway.apiKeys = [ + { name: 'my-api-key', value: 'my-secret-value' }, + ]; + return serverlessStepFunctions.compileApiKeys().then(() => { + const resource = serverlessStepFunctions.serverless.service.provider + .compiledCloudFormationTemplate.Resources[ + serverlessStepFunctions.provider.naming.getApiKeyLogicalId(1) + ]; + expect(resource.Type).to.equal('AWS::ApiGateway::ApiKey'); + expect(resource.Properties.Name).to.equal('my-api-key'); + expect(resource.Properties.Value).to.equal('my-secret-value'); + expect(resource.Properties.Enabled).to.equal(true); + }); + }); + + it('should compile api key resource when apiKey is an object with name only', () => { + serverlessStepFunctions.serverless.service.provider.apiGateway.apiKeys = [ + { name: 'my-api-key' }, + ]; + return serverlessStepFunctions.compileApiKeys().then(() => { + const resource = serverlessStepFunctions.serverless.service.provider + .compiledCloudFormationTemplate.Resources[ + serverlessStepFunctions.provider.naming.getApiKeyLogicalId(1) + ]; + expect(resource.Type).to.equal('AWS::ApiGateway::ApiKey'); + expect(resource.Properties.Name).to.equal('my-api-key'); + expect(resource.Properties.Value).to.equal(undefined); + }); + }); });