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

feat(braze): allowing braze content proxy to access lists #705

Merged
merged 7 commits into from
Aug 29, 2024
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
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
Loading