Skip to content

Commit

Permalink
feat(braze): allowing braze content proxy to access lists (#705)
Browse files Browse the repository at this point in the history
* feat(jwt): moving jwt generation to its own package

* feat(digest): adding in digest outline

* fix(jwt): adding in jwt generation for a user

* feat(braze): adding in basic braze list query

* test(braze): testing the ins and out of digest
  • Loading branch information
bassrock authored Aug 29, 2024
1 parent 3ff2f95 commit 2142851
Show file tree
Hide file tree
Showing 46 changed files with 855 additions and 259 deletions.
33 changes: 33 additions & 0 deletions infrastructure/braze-content-proxy/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ class BrazeContentProxy extends TerraformStack {
}): PocketALBApplication {
const { region, caller, secretsManagerKmsAlias, snsTopic, wafAcl } =
dependencies;
const intMaskSecretArn = `arn:aws:secretsmanager:${region.name}:${caller.accountId}:secret:Shared/IntMask`;

return new PocketALBApplication(this, 'application', {
internal: false,
Expand Down Expand Up @@ -198,6 +199,38 @@ class BrazeContentProxy extends TerraformStack {
name: 'BRAZE_API_KEY',
valueFrom: `arn:aws:secretsmanager:${region.name}:${caller.accountId}:secret:${config.name}/${config.environment}/BRAZE_API_KEY:key::`,
},
{
name: 'BRAZE_PRIVATE_KEY',
valueFrom: `arn:aws:secretsmanager:${region.name}:${caller.accountId}:secret:${config.name}/${config.environment}/PRIVATE_KEY:::`,
},
{
name: 'CONTACT_HASH',
valueFrom: `${intMaskSecretArn}:contactHash::`,
},
{
name: 'CHARACTER_MAP',
valueFrom: `${intMaskSecretArn}:characterMap::`,
},
{
name: 'POSITION_MAP',
valueFrom: `${intMaskSecretArn}:positionMap::`,
},
{
name: 'MD5_RANDOMIZER',
valueFrom: `${intMaskSecretArn}:md5Randomizer::`,
},
{
name: 'LETTER_INDEX',
valueFrom: `${intMaskSecretArn}:letterIndex::`,
},
{
name: 'SALT_1',
valueFrom: `${intMaskSecretArn}:salt1::`,
},
{
name: 'SALT_2',
valueFrom: `${intMaskSecretArn}:salt2::`,
},
],
},
],
Expand Down
2 changes: 1 addition & 1 deletion lambdas/fxa-webook-proxy-gateway/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@types/aws-lambda": "8.10.143",
"@types/jest": "29.5.12",
"@types/jsonwebtoken": "^9.0.5",
"@types/jwk-to-pem": "^2.0.1",
"@types/jwk-to-pem": "2.0.3",
"@types/node": "^20.16",
"jest": "29.7.0",
"nock": "14.0.0-beta.11",
Expand Down
6 changes: 2 additions & 4 deletions lambdas/fxa-webook-proxy-sqs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,16 @@
"test-integrations": "jest \"\\.integration\\.ts\" --runInBand"
},
"dependencies": {
"@aws-sdk/client-secrets-manager": "3.632.0",
"@aws-sdk/client-sqs": "3.632.0",
"@pocket-tools/jwt-utils": "workspace:*",
"@pocket-tools/lambda-secrets": "workspace:*",
"@sentry/aws-serverless": "8.27.0",
"jsonwebtoken": "^9.0.0",
"jwk-to-pem": "^2.0.5",
"tslib": "2.6.3"
},
"devDependencies": {
"@pocket-tools/eslint-config": "workspace:*",
"@types/aws-lambda": "8.10.143",
"@types/jest": "29.5.12",
"@types/jsonwebtoken": "^9.0.5",
"@types/node": "^20.16",
"jest": "29.7.0",
"nock": "14.0.0-beta.11",
Expand Down
17 changes: 14 additions & 3 deletions lambdas/fxa-webook-proxy-sqs/src/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import * as fx from './index';
import config from './config';
import nock from 'nock';
import * as jwt from './jwt';
import * as secretManager from './secretManager';
import * as mutations from './mutations';

describe('SQS Event Handler', () => {
let handleMutationErrorsSpy;

beforeAll(() => {
jest.spyOn(secretManager, 'getFxaPrivateKey').mockResolvedValue('fake_key');
jest.spyOn(jwt, 'generateJwt').mockReturnValue('fake_token');
jest.spyOn(secretManager, 'getFxaPrivateKey').mockResolvedValue({
p: '2NE9Yskv7kZaM_OMvKElEWRKi6peRae3JkMp-TvjqMIO69kV3zQfpb0gfIdcC54_BuGUUUjL9IEDApWas-IBbG33bKoGTzCzNbfML0aQvAHpuvZI6pGAq3OdHgC-kGjb5wyK3tDaP-rS8aVYjrB9jQY7Go-F4xWyikNm-99BJg0',
kty: 'RSA',
q: 't8a8oOBF-MGnIuQBYlMzUa0YdpnQY2zLOfkocEoRbUNtaUZW-UEwaqy2q9rbQksM6j9LVY8jAzb0YvAag8TorCZlbhvmlZONqq5I_Reto1FPRNXLGJjHVMTonLRboCiSm_EFisZHPvgqAxln00MNAqRQnUnbP5CbCY4RrdNXjTU',
d: 'h5bNYEjOE7wRUms-2mawI6MEqy5F1GmT8uZeVzEeGxfBHmPk2zVipN_YrmbNxCfyxKX_kbY2NbwcCBhUUs7_-v0D5JtJrr2fPEOQAi6snaHal264h5xXv6_Z_nQOYkEp8OYreNWrt9heG2DGPhNlHBEn-yVxcEw9KFl4ABwQhFdzf2PuyTytITlLjqrUWTYDciH3LJSnRyFiO45mii3RvJFmcivSFyyXiH-IFGC60ZyWYswHE8ITD9tENUX5vC-PTLMN71AIaXoGRNHaFHfsJmxbtwPBXkSShk5CRc-YqVNQvDX35KFFx0qnPd5ARWPi9iTzbP4Zyx3eoN37G8eTUQ',
e: 'AQAB',
use: 'sig',
kid: 'helloworld',
qi: 'PJ5W_ANyXuLmsMuCDPlhF8q3G490j3VbxqwjRPKeboxCinAskm7VnQJjZJPBw0_A565YJeEOWjbfauBax-4YaHmOK6wYd1sfTXSq6r5id58fWMmSu8ToZe8sziN5R9kvmrIKrddnS5NtvDQIaZJRUpbfMEzN8JouC--Oylzfwrs',
dp: 'uamznzwYxzmHVKViBsUXMOVo0GB7iboso58v-jTGpmRG0r96cz_3Ob3Sa9CdiXVhE0tn7pMf06gGI9hoOVF3Vpp0HaEa9gUF8SIKvxD2L4iT1X3Awt0GCcte56pLhO3GIPwkjtjZi5JSQIsOYmHPoUuMoRn11Jdn4-4D6fsrlqE',
alg: 'RS256',
dq: 'HG5vokfwK1LyY5B4sliC2QD5hue2-JrNOhPU8MJUvd2voJjUPc2bCvXbcOzz_OaVgev24K67UPUAjAnvYDFnebKbAJTqcHuacCx0eEtgfqLGq7STriN8ux2Xix7QChAc1mlMXTLdtN05yq70hBecfKslGaBifgwGIE1NaOIIan0',
n: 'm6XkeQIGIK44RK44g__-UwzW2cApDNy1H2dCnisrYmJj8QuyEBcFQs9y8PZtYTV3u1fm9awVs-E_SNqy62I6IaTaDwABetjQSNV1-q0NgwpBjcvwldNc2gyt9NNvxE5Yto5RKolZejkAU4GcPgNXah3fgoGZ59IJLVLDl9y9dnYtQwhHZ08k0RqsWTtQTUU9DFN6N7c9d0mOMCet8HbvcTYpT7zcRjAwplpvmo2TAN3iiNRlalyGrxNx2NECewsrDz7oiCutppWUWSa0oIJc0xRGegx4zOMEyPd72Z2Q6-JcxCKjcAIRknOhGyp3pMZZT3lTuoSYK0kbDDFlv90JsQ',
});
});

afterAll(() => {
Expand Down
57 changes: 0 additions & 57 deletions lambdas/fxa-webook-proxy-sqs/src/jwt.spec.ts

This file was deleted.

36 changes: 26 additions & 10 deletions lambdas/fxa-webook-proxy-sqs/src/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { SQSRecord } from 'aws-lambda';

import config from './config';
import { getFxaPrivateKey } from './secretManager';
import { generateJwt } from './jwt';

import { generateJwt, PocketJWK } from '@pocket-tools/jwt-utils';
import { FxaEvent } from '.';

// should match the reasons defined in user-api subgraph schema:
Expand All @@ -12,6 +13,22 @@ enum ExpireUserWebSessionReason {
LOGOUT = 'LOGOUT',
}

/**
*
* @param userId User id to generate a jwt for
* @returns a jwt
*/
const generateFxAJWT = async (userId: string) => {
const privateKey = (await getFxaPrivateKey()) as unknown as PocketJWK;
return generateJwt(privateKey, {
sub: userId,
issuer: config.jwt.iss,
apiId: config.app.apiId,
applicationName: config.app.applicationName,
aud: config.jwt.aud,
});
};

/**
* If a request to client-api was made, handle any potential errors
* @param record SQS record
Expand Down Expand Up @@ -45,8 +62,7 @@ export function handleMutationErrors(
* @param id FxA account ID to delete from Pocket's database
*/
export async function submitDeleteMutation(id: string): Promise<any> {
const privateKey = await getFxaPrivateKey();

const jwt = await generateFxAJWT(id);
const deleteMutation = `
mutation deleteUser($id: ID!) {
deleteUserByFxaId(id: $id)
Expand All @@ -58,7 +74,7 @@ export async function submitDeleteMutation(id: string): Promise<any> {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${generateJwt(privateKey, id)}`,
Authorization: `Bearer ${jwt}`,
'apollographql-client-name': config.app.applicationName,
'apollographql-client-version': config.app.version,
},
Expand All @@ -77,7 +93,7 @@ export async function migrateAppleUserMutation(
email: string,
transferSub: string,
): Promise<any> {
const privateKey = await getFxaPrivateKey();
const jwt = await generateFxAJWT(id);

const migrateAppleUser = `
mutation migrateAppleUser($fxaId: ID!, $email: String!) {
Expand All @@ -90,7 +106,7 @@ export async function migrateAppleUserMutation(
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${generateJwt(privateKey, id)}`,
Authorization: `Bearer ${jwt}`,
transfersub: transferSub,
'apollographql-client-name': config.app.applicationName,
'apollographql-client-version': config.app.version,
Expand All @@ -109,7 +125,7 @@ export async function submitEmailUpdatedMutation(
id: string,
email: string,
): Promise<any> {
const privateKey = await getFxaPrivateKey();
const jwt = await generateFxAJWT(id);

const updateUserEmailMutation = `mutation UpdateUserEmailByFxaId($id: ID!, $email: String!) {updateUserEmailByFxaId(id: $id, email: $email) {
email
Expand All @@ -122,7 +138,7 @@ export async function submitEmailUpdatedMutation(
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${generateJwt(privateKey, id)}`,
Authorization: `Bearer ${jwt}`,
'apollographql-client-name': config.app.applicationName,
'apollographql-client-version': config.app.version,
},
Expand All @@ -136,7 +152,7 @@ export async function submitEmailUpdatedMutation(
* @param id FxA account ID
*/
export async function passwordChangeMutation(id: string): Promise<any> {
const privateKey = await getFxaPrivateKey();
const jwt = await generateFxAJWT(id);

const expireUserWebSessionByFxaId = `
mutation ExpireUserWebSessionByFxaId($id: ID!, $reason: ExpireUserWebSessionReason!) {
Expand All @@ -152,7 +168,7 @@ export async function passwordChangeMutation(id: string): Promise<any> {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${generateJwt(privateKey, id)}`,
Authorization: `Bearer ${jwt}`,
'apollographql-client-name': config.app.applicationName,
'apollographql-client-version': config.app.version,
},
Expand Down
16 changes: 2 additions & 14 deletions lambdas/fxa-webook-proxy-sqs/src/secretManager.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
import {
GetSecretValueCommand,
SecretsManagerClient,
} from '@aws-sdk/client-secrets-manager';
import config from './config';

const client = new SecretsManagerClient({ region: config.aws.region });
import { fetchSecret } from '@pocket-tools/lambda-secrets';

//https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-secrets-manager/classes/getsecretvaluecommand.html
export async function getFxaPrivateKey() {
try {
const secret = await client.send(
new GetSecretValueCommand({
SecretId: config.jwt.key,
}),
);

const privateKey = secret.SecretString as string;
return JSON.parse(privateKey);
return await fetchSecret(config.jwt.key);
} catch (e) {
throw new Error('unable to fetch private key' + e);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,4 @@
"graphql": "16.8.1",
"graphql-tag": "2.12.6"
}
}
}
3 changes: 1 addition & 2 deletions packages/image-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,5 @@
"tsconfig": "workspace:*",
"tsup": "8.2.4",
"typescript": "5.5.4"
},
"peerDependencies": {}
}
}
8 changes: 8 additions & 0 deletions packages/jwt-utils/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
src
.github
.idea
.prettier*
.eslintrc.js
.tsconfig.js
*.spec.ts
*.integration.ts
3 changes: 3 additions & 0 deletions packages/jwt-utils/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# JWT Utils

We use this repository as a place to keep code we use across our backend services that implement anything to do with JWTs
3 changes: 3 additions & 0 deletions packages/jwt-utils/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import packages from '@pocket-tools/eslint-config/packages';
import tseslint from 'typescript-eslint';
export default tseslint.config(...packages);
7 changes: 7 additions & 0 deletions packages/jwt-utils/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(jest|spec).[jt]s?(x)'],
testPathIgnorePatterns: ['/dist/'],
setupFilesAfterEnv: ['jest-extended/all'],
};
Loading

0 comments on commit 2142851

Please sign in to comment.