Socket
Socket
Sign inDemoInstall

aws-apigw-authorizer

Package Overview
Dependencies
48
Maintainers
1
Versions
23
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.1.13 to 0.1.14

21

lib/authorizer.d.ts
import * as AWSLambda from './lambda';
export declare type PolicyBuilderFunction = (event: AWSLambda.CustomAuthorizerEvent, principal: string, decodedToken?: Jwt) => AWSLambda.PolicyDocument;
export declare type ContextBuilderFunction = (event: AWSLambda.CustomAuthorizerEvent, principal: string, decodedToken?: Jwt) => AWSLambda.AuthResponseContext;
export declare type PolicyBuilderFunction = (event: AWSLambda.CustomAuthorizerEvent, principalId: string, decodedToken?: Jwt) => AWSLambda.PolicyDocument | Promise<AWSLambda.PolicyDocument>;
export declare type ContextBuilderFunction = (event: AWSLambda.CustomAuthorizerEvent, principalId: string, decodedToken?: Jwt) => AWSLambda.AuthResponseContext | Promise<AWSLambda.AuthResponseContext> | void;
export declare type CustomAuthChecksFunction = (event: AWSLambda.CustomAuthorizerEvent, principalId: string, decodedToken?: Jwt) => void | Promise<void>;
export declare type PrincipalId = string;
export declare type JwtPrincipalIdSelectorFunction = (event: AWSLambda.CustomAuthorizerEvent, decodedToken?: Jwt) => PrincipalId | Promise<PrincipalId>;
export interface AuthorizerConfig {
policyBuilder?: PolicyBuilderFunction;
contextBuilder?: ContextBuilderFunction;
customAuthChecks?: CustomAuthChecksFunction;
jwtPrincipalIdSelectorFunction?: JwtPrincipalIdSelectorFunction;
}

@@ -12,11 +17,13 @@ export declare type Jwt = string | object;

private contextBuilder;
private customAuthChecks;
private basicAuthenticationEnabled;
private jwtAuthenticationEnabled;
private principalIdSelectorFunction;
constructor(authorizerConfig?: AuthorizerConfig);
private assertSourceIp(event);
private authorize(event, principal, decodedToken?, ...logMessages);
private deny(event, ...logMessages);
private log(event, ...logMessages);
private determineAuthorization(event);
private assertSourceIp;
private authorize;
private deny;
private log;
private determineAuthorization;
handler(event: AWSLambda.CustomAuthorizerEvent, _context: AWSLambda.Context, callback: AWSLambda.Callback): Promise<void>;
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const basicAuthValidator = require("./basic-auth-validator");
const ipRangeCheck = require("ip-range-check");
const envkey_1 = require("./envkey");
const jwtValidator = require("./jwt-validator");
const preventBruteForce = require("./prevent-brute-force");
const awsPolicyLib = require('./aws-policy-lib');

@@ -18,4 +15,8 @@ // It is mandatory to set up ALLOWED_IP_ADDRESSES (0.0.0.0/0 is allowed)

this.jwtAuthenticationEnabled = false;
// parse config
this.policyBuilder = authorizerConfig && authorizerConfig.policyBuilder || defaultBuildPolicy;
this.contextBuilder = authorizerConfig && authorizerConfig.contextBuilder || defaultBuildContext;
this.contextBuilder = authorizerConfig && authorizerConfig.contextBuilder || (() => undefined);
this.customAuthChecks = authorizerConfig && authorizerConfig.customAuthChecks || (() => undefined);
this.principalIdSelectorFunction = authorizerConfig && authorizerConfig.jwtPrincipalIdSelectorFunction || defaultJwtPrincipalIdSelector;
// check environment for configured auth flavors
if (Object.keys(process.env).filter(key => key.startsWith('BASIC_AUTH_USER_')).length) {

@@ -35,8 +36,11 @@ this.basicAuthenticationEnabled = true;

}
authorize(event, principal, decodedToken, ...logMessages) {
const context = this.contextBuilder(event, principal, decodedToken);
const policy = Object.assign({}, this.policyBuilder(event, principal, decodedToken), { context });
async authorize(event, principalId, decodedToken, ...logMessages) {
await this.customAuthChecks(event, principalId, decodedToken);
const policy = await this.policyBuilder(event, principalId, decodedToken);
const context = await this.contextBuilder(event, principalId, decodedToken);
if (context) {
Object.assign(policy, { context });
}
this.log(event, 'Authorized:', ...logMessages);
this.log(event, 'Built policy:', JSON.stringify(policy));
preventBruteForce.registerAuthorization(this.assertSourceIp(event));
return policy;

@@ -46,3 +50,2 @@ }

this.log(event, 'Denied:', ...logMessages);
preventBruteForce.registerDenial(this.assertSourceIp(event));
return 'Unauthorized';

@@ -54,51 +57,44 @@ }

}
determineAuthorization(event) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
console.log(JSON.stringify(event, undefined, 2));
// Sanity check: the Authorization header must be present
if (!event.headers || !event.headers.Authorization) {
throw new Error('Authorization HTTP header not present');
}
// Sanity check: the callers sourceIp should be present
const sourceIp = this.assertSourceIp(event);
// Sanity check: the callers sourceIp should not be attempting a brute force
preventBruteForce.checkBruteForceAttempt(sourceIp);
// Sanity check: the callers sourceIp should be an allowed ip
if (envkey_1.envkey('ALLOWED_IP_ADDRESSES')
.split(',')
.filter((ipRange) => ipRangeCheck(sourceIp, ipRange))
.length === 0) {
throw new Error('Source IP does not match with configured ALLOWED_IP_ADDRESSES');
}
// Validate credentials
const [tokenType, token] = event.headers.Authorization.split(' ');
if (tokenType === 'Bearer' && this.jwtAuthenticationEnabled) {
const decodedToken = yield jwtValidator.validate(token);
const principal = decodedToken['upn'] || decodedToken['email'];
return this.authorize(event, principal, decodedToken, `user ${principal} using JWT`);
}
else if (tokenType === 'Basic' && this.basicAuthenticationEnabled) {
const creds = basicAuthValidator.validate(token);
return this.authorize(event, creds.name, undefined, `user ${creds.name} using Basic Auth`);
}
else {
throw new Error(`Unauthorized: unsupported token type ${tokenType}`);
}
});
async determineAuthorization(event) {
// Sanity check: the Authorization header must be present
if (!event.headers || !event.headers.Authorization) {
throw new Error('Authorization HTTP header not present');
}
// Sanity check: the callers sourceIp should be present
const sourceIp = this.assertSourceIp(event);
// Sanity check: the callers sourceIp should be an allowed ip
if (process.env.ALLOWED_IP_ADDRESSES || ''
.split(',')
.filter((ipRange) => ipRangeCheck(sourceIp, ipRange))
.length === 0) {
throw new Error('Source IP does not match with configured ALLOWED_IP_ADDRESSES');
}
// Validate credentials
const [tokenType, token] = event.headers.Authorization.split(' ');
if (tokenType === 'Bearer' && this.jwtAuthenticationEnabled) {
const decodedToken = await jwtValidator.validate(token);
const principalId = await this.principalIdSelectorFunction(event, decodedToken);
return await this.authorize(event, principalId, decodedToken, `user ${principalId} using JWT`);
}
else if (tokenType === 'Basic' && this.basicAuthenticationEnabled) {
const principalId = basicAuthValidator.validate(token).name;
return await this.authorize(event, principalId, undefined, `user ${principalId} using Basic Auth`);
}
else {
throw new Error(`Unauthorized: unsupported token type ${tokenType}`);
}
}
handler(event, _context, callback) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
try {
const policy = yield this.determineAuthorization(event);
callback(undefined, policy);
}
catch (err) {
callback(this.deny(event, err));
}
});
async handler(event, _context, callback) {
try {
const policy = await this.determineAuthorization(event);
callback(undefined, policy);
}
catch (err) {
callback(this.deny(event, err));
}
}
}
exports.ApiGatewayAuthorizer = ApiGatewayAuthorizer;
function defaultBuildPolicy(event, principal, _decodedToken) {
// this function must generate a policy that is associated with the recognized principal user identifier.
function defaultBuildPolicy(event, principalId, _decodedToken) {
// this function must generate a policy that is associated with the recognized principalId user identifier.
// depending on your use case, you might store policies in a DB, or generate them on the fly

@@ -131,9 +127,17 @@ // keep in mind, the policy is cached for 5 minutes by default (TTL is configurable in the authorizer)

// otherwise it would be denied
const policy = new awsPolicyLib.AuthPolicy(principal, awsAccountId, apiOptions);
const policy = new awsPolicyLib.AuthPolicy(principalId, awsAccountId, apiOptions);
policy.allowMethodWithConditions(awsPolicyLib.AuthPolicy.HttpVerb.ALL, '/*', conditions);
return policy.build();
}
function defaultBuildContext(_event, principal, _decodedToken) {
return { principal };
function defaultJwtPrincipalIdSelector(_event, decodedToken) {
let principalId = 'Undeterminable Principal';
if (decodedToken) {
// Different identity providers put different claims on tokens
// Auth0 seems to always put the 'email' claim
// Microsoft seems to always put the e-mail address in 'upn' claim
// Last resort is the 'sub' claim which should mostly be present but contains an ID specific to the identity provider
principalId = decodedToken['email'] || decodedToken['upn'] || decodedToken['sub'] || principalId;
}
return principalId;
}
//# sourceMappingURL=authorizer.js.map

@@ -0,0 +0,0 @@ /**

export declare function validate(token: string): any;

@@ -0,0 +0,0 @@ "use strict";

declare module 'ip-range-check';
export declare function envkey(key: string): string;

@@ -0,0 +0,0 @@ "use strict";

export declare function validate(jwtToken: string): Promise<string | object>;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const jwt = require("jsonwebtoken");
const jwksClient = require("jwks-rsa");
const envkey_1 = require("./envkey");
let _jwksClient;
let _jwksClientUri;
function getSigningKey(jwksUri, kid) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
if (!_jwksClient || jwksUri !== _jwksClientUri || process.env.JWKS_NO_CACHE) {
_jwksClientUri = jwksUri;
_jwksClient = jwksClient({ cache: true, rateLimit: true, jwksUri });
}
return new Promise((resolve, reject) => {
_jwksClient.getSigningKey(kid, (err, jwk) => err ? reject(err) : resolve(jwk));
});
async function getSigningKey(jwksUri, kid) {
if (!_jwksClient || jwksUri !== _jwksClientUri || process.env.JWKS_NO_CACHE) {
_jwksClientUri = jwksUri;
_jwksClient = jwksClient({ cache: true, rateLimit: true, jwksUri });
}
return new Promise((resolve, reject) => {
_jwksClient.getSigningKey(kid, (err, jwk) => err ? reject(err) : resolve(jwk));
});
}
function validate(jwtToken) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const expectedAudience = envkey_1.envkey('AUDIENCE_URI');
const expectedIssuer = envkey_1.envkey('ISSUER_URI');
const jwksUri = envkey_1.envkey('JWKS_URI');
const decodedJwtToken = jwt.decode(jwtToken, { complete: true });
if (!decodedJwtToken) {
throw new Error('Cannot parse JWT token');
}
const kid = decodedJwtToken['header'] && decodedJwtToken['header']['kid'];
const jwk = yield getSigningKey(jwksUri, kid);
const signingKey = jwk.publicKey || jwk.rsaPublicKey;
if (!signingKey) {
throw new Error('Cannot determine the key with which the token was signed');
}
const verificationOptions = {
audience: expectedAudience,
issuer: expectedIssuer,
ignoreExpiration: false
};
// For testing purposes JWT expiration can be disregarded using an environment variable
if (['1', 'true', 'TRUE', 'True'].indexOf(process.env.JWT_NO_EXPIRATION || '') > -1) {
verificationOptions.ignoreExpiration = true;
}
// Verify the JWT
// This either rejects (JWT not valid), or resolves withe the decoded token (object or string)
return new Promise((resolve, reject) => {
jwt.verify(jwtToken, signingKey, verificationOptions, (err, decodedJwtToken) => err ? reject(err) : resolve(decodedJwtToken));
});
async function validate(jwtToken) {
const expectedAudience = process.env.AUDIENCE_URI || '';
const expectedIssuer = process.env.ISSUER_URI || '';
const jwksUri = process.env.JWKS_URI || '';
const decodedJwtToken = jwt.decode(jwtToken, { complete: true });
if (!decodedJwtToken) {
throw new Error('Cannot parse JWT token');
}
const kid = decodedJwtToken['header'] && decodedJwtToken['header']['kid'];
const jwk = await getSigningKey(jwksUri, kid);
const signingKey = jwk.publicKey || jwk.rsaPublicKey;
if (!signingKey) {
throw new Error('Cannot determine the key with which the token was signed');
}
const verificationOptions = {
audience: expectedAudience,
issuer: expectedIssuer,
ignoreExpiration: false
};
// For testing purposes JWT expiration can be disregarded using an environment variable
if (['1', 'true', 'TRUE', 'True'].indexOf(process.env.JWT_NO_EXPIRATION || '') > -1) {
verificationOptions.ignoreExpiration = true;
}
// Verify the JWT
// This either rejects (JWT not valid), or resolves withe the decoded token (object or string)
return new Promise((resolve, reject) => {
jwt.verify(jwtToken, signingKey, verificationOptions, (err, decodedJwtToken) => err ? reject(err) : resolve(decodedJwtToken));
});

@@ -50,0 +44,0 @@ }

@@ -0,0 +0,0 @@ // This all stolen and very slightly adapted from: @types/aws-lambda

{
"name": "aws-apigw-authorizer",
"version": "0.1.13",
"version": "0.1.14",
"description": "AWS Lambda Authorizer for API Gateway",

@@ -12,7 +12,7 @@ "main": "lib/authorizer.js",

"tsc": "tsc",
"build": "rm -rf lib && tsc && cp src/*.d.ts src/*.js lib"
"build": "npm run cover && rm -rf lib && tsc && cp src/*.d.ts src/*.js lib"
},
"repository": {
"type": "git",
"url": "git+https://bitbucket.org/mnservices/aws-apigw-authorizer.git"
"url": "git+https://github.com/ottokruse/aws-apigw-authorizer.git"
},

@@ -25,31 +25,31 @@ "author": "pi-team@mn.nl",

"license": "ISC",
"homepage": "https://bitbucket.org/mnservices/aws-apigw-authorizer#readme",
"homepage": "https://github.com/ottokruse/aws-apigw-authorizer",
"dependencies": {
"basic-auth": "^2.0.0",
"ip-range-check": "0.0.2",
"jsonwebtoken": "^8.1.0",
"jsonwebtoken": "^8.3.0",
"jwks-rsa": "^1.2.1",
"tslib": "^1.8.1"
"tslib": "^1.9.2"
},
"devDependencies": {
"@types/chai": "^4.1.0",
"@types/chai": "^4.1.4",
"@types/chai-as-promised": "^7.1.0",
"@types/jsonwebtoken": "^7.2.5",
"@types/mocha": "^2.2.46",
"@types/nock": "^9.1.1",
"@types/node": "^9.3.0",
"@types/jsonwebtoken": "^7.2.7",
"@types/mocha": "^5.2.2",
"@types/nock": "^9.1.3",
"@types/node": "^10.3.3",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"mocha": "^3.5.3",
"nock": "^9.1.6",
"nyc": "^11.4.1",
"ts-node": "^4.1.0",
"typescript": "^2.6.2"
"mocha": "^5.2.0",
"nock": "^9.3.3",
"nyc": "^12.0.2",
"ts-node": "^6.1.1",
"typescript": "^2.9.2"
},
"nyc": {
"check-coverage": true,
"lines": 90,
"statements": 90,
"functions": 90,
"branches": 90,
"lines": 85,
"statements": 85,
"functions": 85,
"branches": 85,
"include": [

@@ -56,0 +56,0 @@ "src/*.ts",

# AWS Lambda Authorizer for API Gateway
## This is a barebone AWS Lambda Authorizer for API Gateway.
## This is an AWS Lambda Authorizer for API Gateway
It can be used as-is, in which case a default AWS IAM policy is used that allows access to all resources in the API using any HTTP method.
This is an implementation in NodeJS of a custom authorizer function for AWS API Gateway. (https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html)
Configure through Lambda environment variables (see below).
This custom authorizer supports these authentication mechanisms:
## Implement an API Gateway Authorizer Lambda functions as follows:
- JWT
- Basic Authentication
In the default configuration this authorizer will grant the user access to invoke all resources of the API using any HTTP method.
Configuration can be provided through Lambda environment variables (see below).
## How to use
Create a Lambda function in AWS using *Node 8.10* runtime and use the following code:
```js

@@ -17,5 +26,14 @@ const lambdaAuthorizer = new (require('aws-apigw-authorizer')).ApiGatewayAuthorizer();

Of course you will have to create a deployment package to include `aws-apigw-authorizer` and it's dependencies.
npm install aws-apigw-authorizer
See instructions here: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html
### Custom Policy Builder
A custom function can be provided for building custom AWS IAM policies. The custom function will be called after succesfull authentication:
```js
// May return promise or synchronous result as below
function customPolicyBuilder(event, principal, decodedJwt) {

@@ -39,3 +57,3 @@ // event: the raw event that the authorizer lambda function receives from API Gateway

"aws:SourceIp": [
"213.149.225.141/32"
"123.456.789.123/32"
]

@@ -50,3 +68,5 @@ }

const lambdaAuthorizer = new (require('aws-apigw-authorizer')).ApiGatewayAuthorizer({ policyBuilder: customPolicyBuilder });
const lambdaAuthorizer = new (require('aws-apigw-authorizer')).ApiGatewayAuthorizer(
{ policyBuilder: customPolicyBuilder }
);

@@ -56,8 +76,11 @@ exports.handler = lambdaAuthorizer.handler.bind(lambdaAuthorizer);

A custom function can be provided for setting the authorization context. The custom function will be called after succesfull authentication:
### Custom Context Builder
A custom function can be provided for setting the authorization context (https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html). The custom function will be called after succesfull authentication:
```js
function customContextBuilder(_event, _principal, decodedToken) {
// May return promise or synchronous result as below
function customContextBuilder(event, principal, decodedToken) {
return {
sub: decodedToken['sub'],
name: decodedToken['sub'],
foo: 'bar'

@@ -67,3 +90,5 @@ }

const authorizer = new(require('aws-apigw-authorizer')).ApiGatewayAuthorizer({ contextBuilder: customContextBuilder});
const authorizer = new (require('aws-apigw-authorizer')).ApiGatewayAuthorizer(
{ contextBuilder: customContextBuilder }
);

@@ -73,3 +98,23 @@ exports.handler = authorizer.handler.bind(authorizer);

If you throw an error anywhere in the customContextBuilder the request will be denied (HTTP 401).
### Custom Auth Checks
A custom function can be provided in which you can include your own checks. If you throw an error anywhere in that function the request will be denied (HTTP 401).
```js
// May return promise or synchronous result as below
function customAuthChecks(event, principal, decodedToken) {
if (!event.headers['X-SHOULD-BE-PRESENT']) {
throw new Error('HTTP header X-SHOULD-BE-PRESENT is required');
}
}
const authorizer = new (require('aws-apigw-authorizer')).ApiGatewayAuthorizer(
{ authChecks: customAuthChecks }
);
exports.handler = authorizer.handler.bind(authorizer);
```
## Configuration through environment variables:

@@ -76,0 +121,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc