Skip to content

Commit

Permalink
Release 5.3.0
Browse files Browse the repository at this point in the history
Support multiple user authorization sources
  • Loading branch information
akincel committed Sep 8, 2023
1 parent fbc8440 commit f4dcda6
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 388 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)

## [5.3.0] - 2023-09-07

### Changed

The `getUserToken()` and `getUserPrincipal()` methods now support multiple sources of for their values

`getUserToken()` in priority order:

1. `request.authorizerContext.accessToken` (new)
2. `request.authorizerContext.jwt`
3. `request.headers.Authorization`

`getUserPrincipal()` in priority order:

1. `authorizerContext.principalId` (new)
2. `authorizerContext.canonicalId`
3. `request.headers.Authorization`

## [5.2.2] - 2023-08-25

### Added
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lambda-essentials-ts",
"version": "5.2.2",
"version": "5.3.0",
"description": "A selection of the finest modules supporting authorization, API routing, error handling, logging and sending HTTP requests.",
"main": "lib/index.js",
"private": false,
Expand Down
13 changes: 8 additions & 5 deletions src/openApi/apiRequestModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ export interface ApiRequest<Body = any, Query = any> {
route: string;
isBase64Encoded: Boolean;
requestContext: {
authorizer?: {
jwt: string;
canonicalId: string;
principalId: string;
};
authorizer?: AuthorizerContext;
requestId: string;
};
headers: Record<string, string>;
Expand All @@ -19,6 +15,13 @@ export interface ApiRequest<Body = any, Query = any> {
multiValueQueryStringParameters?: any;
}

export interface AuthorizerContext {
jwt?: string;
accessToken?: string;
canonicalId?: string;
principalId?: string;
}

export interface PostRequest<Body = any, Query = any> extends ApiRequest<Body, Query> {
body: Body;
}
Expand Down
40 changes: 32 additions & 8 deletions src/openApi/openApiWrapper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import OpenApi from 'openapi-factory';
import * as uuid from 'uuid';
import { ApiRequest } from './apiRequestModel';
import { ApiRequest, AuthorizerContext } from './apiRequestModel';
import { ApiResponse } from './apiResponseModel';
import { Exception } from '../exceptions/exception';
import { safeJwtCanonicalIdParse, serializeObject } from '../util';
Expand Down Expand Up @@ -41,13 +41,13 @@ export default class OpenApiWrapper {
const correlationId = this.generateCorrelationId(request.headers);
requestLogger.startInvocation(null, correlationId);

// TODO: restrict the alternative way of resolving token and principal only for localhost
this.userToken =
request.requestContext.authorizer?.jwt ?? request.headers.Authorization?.split(' ')[1];
this.userPrincipal =
request.requestContext.authorizer?.canonicalId ??
safeJwtCanonicalIdParse(this.userToken) ??
'unknown';
const userData = this.determineUserData(
request.headers,
request.requestContext.authorizer,
);
this.userToken = userData.userToken ?? this.notSet;
this.userPrincipal = userData.userPrincipal ?? this.notSet;

this.requestId = request.requestContext.requestId;
requestLogger.log({
title: 'RequestLogger',
Expand Down Expand Up @@ -161,4 +161,28 @@ export default class OpenApiWrapper {
this.correlationId = existingCorrelationId ?? uuid.v4();
return this.correlationId;
}

private determineUserData(
headers: Record<string, string>,
authorizerContext?: AuthorizerContext,
): {
userToken?: string;
userPrincipal?: string;
} {
if (authorizerContext) {
return {
userPrincipal: authorizerContext.principalId ?? authorizerContext.canonicalId,
userToken: authorizerContext.accessToken ?? authorizerContext.jwt,
};
}

if (headers.Authorization) {
const userToken = headers.Authorization.split(' ')?.[1];
const userPrincipal = safeJwtCanonicalIdParse(userToken);

return { userToken, userPrincipal };
}

return { userToken: undefined, userPrincipal: undefined };
}
}
53 changes: 50 additions & 3 deletions tests/openApi/openApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ describe('Open API Wrapper', () => {
const path = '/path';
const principalId = 'tests-principal-id';
const canonicalId = 'tests-canonical-id';
const jwt = 'tests-jwt';
const accessToken = 'test-access-token';
const correlationId = 'test-correlation-id';
const request: ApiRequest<any> = {
headers,
Expand All @@ -22,9 +24,8 @@ describe('Open API Wrapper', () => {
pathParameters: { param: 'param' },
requestContext: {
authorizer: {
canonicalId,
jwt: 'tests-jwt',
principalId,
accessToken,
},
requestId: 'tests-request-id',
},
Expand All @@ -41,6 +42,28 @@ describe('Open API Wrapper', () => {
'orion-correlation-id-root': correlationId,
},
};
const requestWithOldStyleAuthorizer: ApiRequest<any> = {
...request,
requestContext: {
authorizer: {
canonicalId,
jwt,
},
requestId: 'tests-request-id',
},
};
const testJwt =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaHR0cHM6Ly9jbGFpbXMuY2ltcHJlc3MuaW8vY2Fub25pY2FsX2lkIjoiam9obkBkb2Uub3JnIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.XNjjaJDz4g8AecLBIDZY6aDwANCNMKg2NrcNxaJ-0JaqoGm0fBGPCZfbtGuf4-8DVqnwmrWslt7tMEj8QIU_TL1cWsX83ZGggM4crGva8tLw54Vhg5BrNWCOBiMphxGzU-5DbXPWvtnWatJgDdBuRSegZK5slpa8DnmXiMNkXxZhyulTbZYkArE2e16NFZhVANWmR3A4K_0ETF-s3uARvua9rPOxkaaxHPIkoZ58CsuD1p6pqi8KDthiW0OCry6o2uPIG-MfyP0gKDPD88XtVD5pcr6WWhNv37ZnucG75wuxE8c6eMj_pPCrt_eoM8ygUc9GY7XoLmZZAvI-szlivw';
const requestWithAuthorizationHeader: ApiRequest<any> = {
...request,
headers: {
Authorization: `Bearer ${testJwt}`,
},
requestContext: {
authorizer: undefined,
requestId: 'tests-request-id',
},
};

beforeEach(() => {
LoggerMock.mockImplementation(() => ({
Expand All @@ -60,11 +83,35 @@ describe('Open API Wrapper', () => {
level: 'INFO',
title: 'RequestLogger',
method: httpMethod,
user: canonicalId,
user: principalId,
path,
headers,
});
});

test('sets userToken and userPrincipal from new-style Authorizer', async () => {
const openApi = new OpenApiWrapper(new LoggerMock());
await openApi.api.requestMiddleware(request);

expect(openApi.getUserToken()).toEqual(accessToken);
expect(openApi.getUserPrincipal()).toEqual(principalId);
});

test('sets userToken and userPrincipal from old-style Authorizer', async () => {
const openApi = new OpenApiWrapper(new LoggerMock());
await openApi.api.requestMiddleware(requestWithOldStyleAuthorizer);

expect(openApi.getUserToken()).toEqual(jwt);
expect(openApi.getUserPrincipal()).toEqual(canonicalId);
});

test('sets userToken and userPrincipal from Authorization header', async () => {
const openApi = new OpenApiWrapper(new LoggerMock());
await openApi.api.requestMiddleware(requestWithAuthorizationHeader);

expect(openApi.getUserToken()).toEqual(testJwt);
expect(openApi.getUserPrincipal()).toEqual('[email protected]');
});
});

describe('responseMiddleware', () => {
Expand Down
Loading

0 comments on commit f4dcda6

Please sign in to comment.