Skip to content
Merged
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
38 changes: 37 additions & 1 deletion .projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion .projenrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ const { TaimosTypescriptLibrary } = require('@taimos/projen');
const project = new TaimosTypescriptLibrary({
name: '@taimos/lambda-toolbox',
deps: [
'aws-sdk',
'jsonwebtoken',
'jwk-to-pem',
'axios',
'uuid',
'lambda-log',
'@aws-crypto/sha256-js',
'@aws-sdk/client-appsync',
'@aws-sdk/client-dynamodb',
'@aws-sdk/credential-providers',
'@aws-sdk/lib-dynamodb',
'@aws-sdk/node-http-handler',
'@aws-sdk/protocol-http',
'@aws-sdk/signature-v4',
],
docgen: false,
defaultReleaseBranch: 'main',
Expand All @@ -21,6 +28,8 @@ const project = new TaimosTypescriptLibrary({
'@types/uuid',
'@taimos/projen',
'@hapi/boom',
'aws-sdk-client-mock',
'aws-sdk-client-mock-jest',
],
keywords: [
'aws',
Expand Down
11 changes: 10 additions & 1 deletion package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 25 additions & 12 deletions src/client/appsync.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { parse, UrlWithStringQuery } from 'url';
import * as AWS from 'aws-sdk';
import { URL } from 'url';
import { Sha256 } from '@aws-crypto/sha256-js';
import * as credentials from '@aws-sdk/credential-providers';
import { HttpRequest } from '@aws-sdk/protocol-http';
import { SignatureV4 } from '@aws-sdk/signature-v4';
import * as axios from 'axios';

export class AppSyncClient {

protected readonly graphQlServerUri: UrlWithStringQuery;
protected readonly graphQlServerUri: URL;

constructor(protected readonly graphQlServerUrl: string, protected readonly awsRegion: string) {
this.graphQlServerUri = parse(this.graphQlServerUrl);
this.graphQlServerUri = new URL(this.graphQlServerUrl);
if (!this.graphQlServerUri.href) {
throw new Error('Invalid GraphQL server URL');
}
Expand All @@ -20,22 +23,32 @@ export class AppSyncClient {
variables,
};

const httpRequest = new AWS.HttpRequest(new AWS.Endpoint(this.graphQlServerUri.href!), this.awsRegion);
const httpRequest = new HttpRequest({
headers: {
'host': this.graphQlServerUri.host!,
'Content-Type': 'application/json',
},
method: 'POST',
body: JSON.stringify(post_body),
});
httpRequest.headers.host = this.graphQlServerUri.host!;
httpRequest.headers['Content-Type'] = 'application/json';
httpRequest.method = 'POST';
httpRequest.body = JSON.stringify(post_body);

await ((AWS.config.credentials as AWS.Credentials)?.getPromise());
// There's now an official signature library - yay!
const signer = new SignatureV4({
credentials: credentials.fromEnv(),
service: 'appsync',
region: this.awsRegion,
sha256: Sha256,
});

// Signers is an internal API
const signer = new (AWS as any).Signers.V4(httpRequest, 'appsync', true);
signer.addAuthorization(AWS.config.credentials, (AWS as any).util.date.getDate());
const signedRequest = await signer.sign(httpRequest, { signingDate: new Date() });

const res = await axios.default.post(this.graphQlServerUri.href!, httpRequest.body, {
headers: httpRequest.headers,
return axios.default.post(this.graphQlServerUri.href!, signedRequest.body, {
headers: signedRequest.headers,
});
return res;
}

}
47 changes: 26 additions & 21 deletions src/dynamodb/client.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,41 @@
import * as https from 'https';
import { env } from 'process';
import { DynamoDB } from 'aws-sdk';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import * as dynamodb from '@aws-sdk/lib-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
import { NodeHttpHandler } from '@aws-sdk/node-http-handler';
import { PrimaryEntity } from './model';

const agent = new https.Agent({
keepAlive: true,
});
export const dynamoClient: DynamoDB.DocumentClient = new DynamoDB.DocumentClient({ httpOptions: { agent } });
export const dynamoClient: DynamoDBDocumentClient = DynamoDBDocumentClient.from(new DynamoDBClient({
requestHandler: new NodeHttpHandler({ httpsAgent: agent }),
}));

export const TABLE_NAME: string = env.TABLE!;

export async function getItem<E extends PrimaryEntity<any, any>>(pk: E['PK'], sk: E['SK'], options?: Omit<DynamoDB.DocumentClient.GetItemInput, 'TableName' | 'Key'>): Promise<E | undefined> {
const res = await dynamoClient.get({
export async function getItem<E extends PrimaryEntity<any, any>>(pk: E['PK'], sk: E['SK'], options?: Omit<dynamodb.GetCommandInput, 'TableName' | 'Key'>): Promise<E | undefined> {
const res = await dynamoClient.send(new dynamodb.GetCommand({
TableName: TABLE_NAME,
Key: {
PK: pk,
SK: sk,
},
...options,
}).promise();
}));
return res.Item ? res.Item as E : undefined;
}

export async function deleteItem<E extends PrimaryEntity<any, any>>(pk: E['PK'], sk: E['SK'], options?: Omit<DynamoDB.DocumentClient.DeleteItemInput, 'TableName' | 'Key'>): Promise<void> {
await dynamoClient.delete({
export async function deleteItem<E extends PrimaryEntity<any, any>>(pk: E['PK'], sk: E['SK'], options?: Omit<dynamodb.DeleteCommandInput, 'TableName' | 'Key'>): Promise<void> {
await dynamoClient.send(new dynamodb.DeleteCommand({
TableName: TABLE_NAME,
Key: {
PK: pk,
SK: sk,
},
...options,
}).promise();
}));
}

export async function putNewItem<E extends PrimaryEntity<any, any>>(pk: E['PK'], sk: E['SK'], item: Omit<E, 'PK' | 'SK'>): Promise<E> {
Expand All @@ -39,35 +44,35 @@ export async function putNewItem<E extends PrimaryEntity<any, any>>(pk: E['PK'],
SK: sk,
...item,
};
await dynamoClient.put({
await dynamoClient.send(new dynamodb.PutCommand({
TableName: TABLE_NAME,
Item,
ConditionExpression: 'attribute_not_exists(PK) and attribute_not_exists(SK)',
}).promise();
}));
return Item as E;
}

export async function updateExistingItem<E extends PrimaryEntity<any, any>>(pk: E['PK'], sk: E['SK'], item: Partial<E>): Promise<E | undefined> {
const res = await dynamoClient.update(createUpdate<E>({
const res = await dynamoClient.send(createUpdate<E>({
Key: {
PK: pk,
SK: sk,
},
ConditionExpression: 'attribute_exists(PK) and attribute_exists(SK)',
ReturnValues: 'ALL_NEW',
}, item)).promise();
}, item));
return res.Attributes ? res.Attributes as E : undefined;
}

export async function pagedQuery<T>(query: Omit<DynamoDB.DocumentClient.QueryInput, 'TableName'>): Promise<T[]> {
export async function pagedQuery<T>(query: Omit<dynamodb.QueryCommandInput, 'TableName'>): Promise<T[]> {
let startKey;
const result: T[] = [];
do {
const res: DynamoDB.DocumentClient.QueryOutput = await dynamoClient.query({
const res: dynamodb.QueryCommandOutput = await dynamoClient.send(new dynamodb.QueryCommand({
...query,
TableName: TABLE_NAME,
ExclusiveStartKey: startKey,
}).promise();
}));
if (res.Items) {
result.push(...res.Items as T[]);
}
Expand All @@ -76,15 +81,15 @@ export async function pagedQuery<T>(query: Omit<DynamoDB.DocumentClient.QueryInp
return result;
}

export async function pagedScan<T>(query: Omit<DynamoDB.DocumentClient.ScanInput, 'TableName'>): Promise<T[]> {
export async function pagedScan<T>(query: Omit<dynamodb.ScanCommandInput, 'TableName'>): Promise<T[]> {
let startKey;
const result: T[] = [];
do {
const res: DynamoDB.DocumentClient.ScanOutput = await dynamoClient.scan({
const res: dynamodb.ScanCommandOutput = await dynamoClient.send(new dynamodb.ScanCommand({
...query,
TableName: TABLE_NAME,
ExclusiveStartKey: startKey,
}).promise();
}));
if (res.Items) {
result.push(...res.Items as T[]);
}
Expand All @@ -101,7 +106,7 @@ export function padLeftZeros(val: number | string | undefined) {
return ('00' + val).slice(-2);
}

export function createUpdate<T>(request: Omit<DynamoDB.DocumentClient.UpdateItemInput, 'TableName'>, data: Partial<T>): DynamoDB.DocumentClient.UpdateItemInput {
export function createUpdate<T>(request: Omit<dynamodb.UpdateCommandInput, 'TableName'>, data: Partial<T>): dynamodb.UpdateCommand {
const fieldsToSet = [];
const fieldsToRemove = [];
const expressionNames: any = {};
Expand Down Expand Up @@ -129,7 +134,7 @@ export function createUpdate<T>(request: Omit<DynamoDB.DocumentClient.UpdateItem
if (request.UpdateExpression) {
update += request.UpdateExpression;
}
return {
return new dynamodb.UpdateCommand({
...request,
TableName: TABLE_NAME,
UpdateExpression: update,
Expand All @@ -147,5 +152,5 @@ export function createUpdate<T>(request: Omit<DynamoDB.DocumentClient.UpdateItem
...expressionValues,
},
},
};
});
}
3 changes: 1 addition & 2 deletions src/http/auth.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { env } from 'process';
import Axios from 'axios';
import { verify, JwtHeader, SigningKeyCallback } from 'jsonwebtoken';
// import jwkToPem = require('jwk-to-pem');
import { JwtHeader, SigningKeyCallback, verify } from 'jsonwebtoken';
import jwkToPem from 'jwk-to-pem';
import logger from 'lambda-log';
import { ForbiddenError, UnauthenticatedError } from '../types/errors';
Expand Down
Loading