Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: eliminate poor abstrction in lambda hrm auth package. #516

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,23 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/

import { newOk, isErr } from '@tech-matters/types';
import { newOk } from '@tech-matters/types';
import {
HrmAuthenticateParameters,
HrmAuthenticateResult,
authenticate,
HRMAuthenticationObjectTypes,
} from './index';
import callHrmApi from './callHrmApi';

export const mockBuckets = ['mock-bucket'];
HrmAuthenticateResult,
} from '@tech-matters/hrm-authentication';

export const fileTypes = {
recording: 'Recording',
transcript: 'ExternalTranscript',
document: 'Case',
} as const;
import { fileTypes, FileTypes, Parameters } from './parseParameters';

export type FileTypes = keyof typeof fileTypes;
export const mockBuckets = ['mock-bucket'];

export type FileMethods = 'getObject' | 'putObject' | 'deleteObject';

export type AuthenticateParams = Parameters & {
authHeader: string;
};

export const fileMethods: Record<
HRMAuthenticationObjectTypes,
Partial<Record<FileMethods, string>>
Expand Down Expand Up @@ -77,34 +74,29 @@ export type HrmAuthenticateFilesUrlsRequestData = {
fileType: FileTypes;
};

export const authUrlPathGenerator = ({
const authenticateFilesUrls = async ({
accountSid,
method,
fileType,
objectId,
objectType,
requestData: { fileType, method },
}: HrmAuthenticateParameters) => {
const permission = getPermission({ objectType, fileType, method });

return `v0/accounts/${accountSid}/permissions/${permission}`;
};

const filesUrlsAuthenticator = async (
params: HrmAuthenticateParameters,
): Promise<HrmAuthenticateResult> => {
const {
objectId,
objectType,
authHeader,
requestData: { bucket, key },
} = params;

authHeader,
bucket,
key,
}: AuthenticateParams): Promise<HrmAuthenticateResult> => {
// This is a quick and dirty way to lock this down so we can test
// with fake data without exposing real data in the test environment.
if (mockBuckets.includes(bucket)) {
return newOk({ data: true });
}

const result = await callHrmApi({
urlPath: authUrlPathGenerator(params),
return authenticate({
accountSid,
permission: getPermission({
objectType,
fileType,
method,
}),
authHeader,
requestData: {
objectType,
Expand All @@ -113,11 +105,6 @@ const filesUrlsAuthenticator = async (
key,
},
});
if (isErr(result)) {
return result;
}

return newOk({ data: true });
};

export default filesUrlsAuthenticator;
export default authenticateFilesUrls;
22 changes: 5 additions & 17 deletions hrm-domain/lambdas/files-urls/getSignedS3Url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

import { AlbHandlerEvent } from '@tech-matters/alb-handler';
import { TResult, newErr, isErr, newOk } from '@tech-matters/types';
import { authenticate } from '@tech-matters/hrm-authentication';
import { getSignedUrl } from '@tech-matters/s3-client';
import authenticate from './authenticate';
import { parseParameters } from './parseParameters';

export type GetSignedS3UrlSuccessResultData = {
Expand Down Expand Up @@ -51,28 +51,16 @@ const getSignedS3Url = async (event: AlbHandlerEvent): Promise<GetSignedS3UrlRes
return parseParametersResult;
}

const { accountSid, bucket, key, method, objectType, objectId, fileType } =
parseParametersResult.data;

const authorization = event.headers?.Authorization || event.headers?.authorization;

const { data: parameters } = parseParametersResult;
const authenticateResult = await authenticate({
accountSid,
objectType,
objectId,
authHeader: convertBasicAuthHeader(authorization!),
type: 'filesUrls',
requestData: {
fileType,
method,
bucket,
key,
},
...parameters,
authHeader: event.headers?.Authorization ?? event.headers?.authorization ?? '',
});
if (isErr(authenticateResult)) {
return authenticateResult;
}

const { bucket, key, method } = parameters;
try {
const getSignedUrlResult = await getSignedUrl({
method,
Expand Down
9 changes: 8 additions & 1 deletion hrm-domain/lambdas/files-urls/parseParameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,18 @@ import { GetSignedUrlMethods, GET_SIGNED_URL_METHODS } from '@tech-matters/s3-cl
import { newErr, newOk, TResult } from '@tech-matters/types';

import {
FileTypes,
HRMAuthenticationObjectTypes,
isAuthenticationObjectType,
} from '@tech-matters/hrm-authentication';

export const fileTypes = {
recording: 'Recording',
transcript: 'ExternalTranscript',
document: 'Case',
} as const;

export type FileTypes = keyof typeof fileTypes;

const objectTypes: Record<HRMAuthenticationObjectTypes, Record<string, string[]>> = {
contact: {
requiredParameters: ['objectId'],
Expand Down
1 change: 0 additions & 1 deletion lambdas/packages/alb-handler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"@types/aws-lambda": "^8.10.108"
},
"scripts": {
"test:integration": "cross-env S3_FORCE_PATH_STYLE=true S3_ENDPOINT=http://localhost:4566 SQS_ENDPOINT=http://localhost:4566 jest tests/integration",
"test:unit": "jest tests/unit"
}
}

This file was deleted.

19 changes: 12 additions & 7 deletions lambdas/packages/hrm-authentication/callHrmApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,21 @@
import { newErr, newOk } from '@tech-matters/types';
import { URLSearchParams } from 'url';

export type CallHrmApiParameters = {
urlPath: string;
export type CallHrmApiParameters<TData> = {
accountSid: string;
permission: string;
authHeader: string;
requestData?: any;
requestData?: TData;
};

const callHrmApi = async ({ urlPath, requestData, authHeader }: CallHrmApiParameters) => {
const params = new URLSearchParams(requestData).toString();
export const callHrmApi = async <TData>({
accountSid,
permission,
requestData,
authHeader,
}: CallHrmApiParameters<TData>) => {
const urlPath = `v0/accounts/${accountSid}/permissions/${permission}`;
const params = requestData ? new URLSearchParams(requestData).toString() : '';
const fullUrl = params
? `${process.env.HRM_BASE_URL}/${urlPath}?${params}`
: `${process.env.HRM_BASE_URL}/${urlPath}`;
Expand All @@ -49,5 +56,3 @@ const callHrmApi = async ({ urlPath, requestData, authHeader }: CallHrmApiParame
const data = await response.json();
return newOk({ data });
};

export default callHrmApi;
45 changes: 10 additions & 35 deletions lambdas/packages/hrm-authentication/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
import { TResult } from '@tech-matters/types';
import filesUrlsAuthenticator, {
HrmAuthenticateFilesUrlsRequestData,
} from './filesUrlsAuthenticator';
import { TResult, newOk, isErr } from '@tech-matters/types';

/**
* The authenticator will call the authenticator based on the type.
* In a perfect world the hrm side of authentication would be a single endpoint
* that would accept a common payload and return a common response.
* And this very leaky abstraction would not be needed.
*
* For now we have to support multiple endpoints and multiple payloads with
* different responses, so the function is basically an adapter.
*
* The goal was to keep all hrm authentication transformations centralized
* in a single place to aid in the future refactoring.
*/
const types = {
filesUrls: (params: HrmAuthenticateParameters) => filesUrlsAuthenticator(params),
};
import { callHrmApi, CallHrmApiParameters } from './callHrmApi';

/**
* The object types that can be authenticated.
Expand All @@ -48,23 +31,15 @@ export const isAuthenticationObjectType = (
type: string,
): type is HRMAuthenticationObjectTypes => Object.keys(objectTypes).includes(type);

export type HrmAuthenticateTypes = keyof typeof types;

export type HrmAuthenticateResult = TResult<true>;

export type HrmAuthenticateParameters = {
accountSid: string;
objectType: HRMAuthenticationObjectTypes;
objectId?: string;
type: HrmAuthenticateTypes;
authHeader: string;
requestData: HrmAuthenticateFilesUrlsRequestData;
};

export const authenticate = async (
params: HrmAuthenticateParameters,
export const authenticate = async <TData>(
params: CallHrmApiParameters<TData>,
): Promise<HrmAuthenticateResult> => {
return types[params.type](params);
};
const result = await callHrmApi<TData>(params);
if (isErr(result)) {
return result;
}

export { FileTypes } from './filesUrlsAuthenticator';
return newOk({ data: true });
};
Loading