From c9435229988bf0b97ade2f10b2ecdfbc8c1ca59e Mon Sep 17 00:00:00 2001 From: Kat Schelonka <34227334+kschelonka@users.noreply.github.com> Date: Wed, 29 Jan 2025 10:59:25 -0800 Subject: [PATCH] feat(deletion): revoke fxa tokens when deleting accounts (#897) * feat(deletion): revoke fxa tokens when deleting accounts This data was deleted before on the Pocket side, but now it will remove Pocket from integrations on the Mozilla account page. [POCKET-9990] * fix(test): add reference to user_firefox_account for test seeds * chore: fix a lost import and nock --- .../src/dataDeleterApp.ts | 12 ++ .../account-data-deleter-events/src/config.ts | 1 + .../src/handlers/accountDelete/index.spec.ts | 4 + .../src/handlers/accountDelete/index.ts | 4 + .../src/handlers/accountDelete/postRequest.ts | 9 ++ pnpm-lock.yaml | 47 ++++---- servers/account-data-deleter/package.json | 1 + .../account-data-deleter/src/config/index.ts | 6 + .../src/dataService/FxaRevoker.spec.ts | 52 +++++++++ .../src/dataService/FxaRevoker.ts | 109 ++++++++++++++++++ .../account-data-deleter/src/routes/index.ts | 1 + .../src/routes/revokeFxa.ts | 33 ++++++ servers/account-data-deleter/src/server.ts | 7 +- servers/user-api/src/config/index.ts | 6 +- servers/user-api/src/test/functional/seeds.ts | 26 +++-- 15 files changed, 282 insertions(+), 36 deletions(-) create mode 100644 servers/account-data-deleter/src/dataService/FxaRevoker.spec.ts create mode 100644 servers/account-data-deleter/src/dataService/FxaRevoker.ts create mode 100644 servers/account-data-deleter/src/routes/revokeFxa.ts diff --git a/infrastructure/account-data-deleter/src/dataDeleterApp.ts b/infrastructure/account-data-deleter/src/dataDeleterApp.ts index 062c47921..ef541b64a 100644 --- a/infrastructure/account-data-deleter/src/dataDeleterApp.ts +++ b/infrastructure/account-data-deleter/src/dataDeleterApp.ts @@ -217,6 +217,18 @@ export class DataDeleterApp extends Construct { name: 'EXPORT_SIGNEDURL_USER_SECRET_KEY', valueFrom: `arn:aws:secretsmanager:${region.name}:${caller.accountId}:secret:${config.name}/${config.environment}/EXPORT_USER_CREDS:secretAccessKey::`, }, + { + name: 'FXA_CLIENT_ID', + valueFrom: `arn:aws:ssm:${region.name}:${caller.accountId}:parameter/Web/${config.environment}/FIREFOX_WEB_AUTH_CLIENT_ID`, + }, + { + name: 'FXA_CLIENT_SECRET', + valueFrom: `arn:aws:ssm:${region.name}:${caller.accountId}:parameter/Web/${config.environment}/FIREFOX_WEB_AUTH_CLIENT_SECRET`, + }, + { + name: 'FXA_OAUTH_URL', + valueFrom: `arn:aws:ssm:${region.name}:${caller.accountId}:parameter/Web/${config.environment}/FIREFOX_AUTH_OAUTH_URL`, + }, ], }, ], diff --git a/lambdas/account-data-deleter-events/src/config.ts b/lambdas/account-data-deleter-events/src/config.ts index 3cbbe45e5..08cc09816 100644 --- a/lambdas/account-data-deleter-events/src/config.ts +++ b/lambdas/account-data-deleter-events/src/config.ts @@ -8,6 +8,7 @@ export const config = { 'https://account-data-deleter-api.getpocket.dev', queueDeletePath: '/queueDelete', stripeDeletePath: '/stripeDelete', + fxaRevokePath: '/revokeFxa', sentry: { // these values are inserted into the environment in // .aws/src/.ts diff --git a/lambdas/account-data-deleter-events/src/handlers/accountDelete/index.spec.ts b/lambdas/account-data-deleter-events/src/handlers/accountDelete/index.spec.ts index da317273b..feb5d68f2 100644 --- a/lambdas/account-data-deleter-events/src/handlers/accountDelete/index.spec.ts +++ b/lambdas/account-data-deleter-events/src/handlers/accountDelete/index.spec.ts @@ -17,6 +17,10 @@ describe('accountDelete handler', () => { }), }; + beforeEach(() => { + nock(config.endpoint).post(config.fxaRevokePath).reply(200); + }); + afterEach(() => { nock.cleanAll(); jest.restoreAllMocks(); diff --git a/lambdas/account-data-deleter-events/src/handlers/accountDelete/index.ts b/lambdas/account-data-deleter-events/src/handlers/accountDelete/index.ts index 105ea9f34..129e707bf 100644 --- a/lambdas/account-data-deleter-events/src/handlers/accountDelete/index.ts +++ b/lambdas/account-data-deleter-events/src/handlers/accountDelete/index.ts @@ -2,6 +2,7 @@ import { AccountDeleteEvent } from '../../schemas/accountDeleteEvent.ts'; import { callQueueDeleteEndpoint, callStripeDeleteEndpoint, + callFxARevokeEndpoint, } from './postRequest.ts'; import { SQSRecord } from 'aws-lambda'; import { AggregateError } from '../../errors/AggregateError.ts'; @@ -47,6 +48,7 @@ export function validatePostBody( * rows from Pocket's internal database * * stripe API - deletes stripe customer data (and internal * stripe-related data in database) + * * FxA Auth API - revokes access tokens from FxA (Mozilla Accounts) * @param record the event forwarded from event bridge via SQS * @throws Error if the record body does not conform to expected schema * @throws AggregateError if any errors encountered making @@ -59,10 +61,12 @@ export async function accountDeleteHandler(record: SQSRecord): Promise { const postBody = validatePostBody(message); const queueRes = await callQueueDeleteEndpoint(postBody); const stripeRes = await callStripeDeleteEndpoint(postBody); + const fxaRes = await callFxARevokeEndpoint(postBody); const errors: Error[] = []; for await (const { endpoint, res } of [ { endpoint: 'queueDelete', res: queueRes }, { endpoint: 'stripeDelete', res: stripeRes }, + { endpoint: 'fxaDelete', res: fxaRes }, ]) { if (!res.ok) { const data = await res.json(); diff --git a/lambdas/account-data-deleter-events/src/handlers/accountDelete/postRequest.ts b/lambdas/account-data-deleter-events/src/handlers/accountDelete/postRequest.ts index 904edede6..d32dfb52d 100644 --- a/lambdas/account-data-deleter-events/src/handlers/accountDelete/postRequest.ts +++ b/lambdas/account-data-deleter-events/src/handlers/accountDelete/postRequest.ts @@ -51,3 +51,12 @@ export async function callQueueDeleteEndpoint(body: any): Promise { export async function callStripeDeleteEndpoint(body: any): Promise { return postRequest(body, config.stripeDeletePath, config.endpoint); } + +/** + * Revoke FxA Access Token when a user deletes their account + * @param body + * @returns + */ +export async function callFxARevokeEndpoint(body: any): Promise { + return postRequest(body, config.fxaRevokePath, config.endpoint); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b3c9baf6..f882ad4e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,7 +36,7 @@ importers: devDependencies: '@commitlint/cli': specifier: 19.6.1 - version: 19.6.1(@types/node@22.10.7)(typescript@5.8.0-dev.20250116) + version: 19.6.1(@types/node@22.10.7)(typescript@5.8.0-dev.20250128) '@commitlint/config-conventional': specifier: ^19.6.0 version: 19.6.0 @@ -45,7 +45,7 @@ importers: version: link:packages/eslint-config syncpack: specifier: ^13.0.0 - version: 13.0.0(typescript@5.8.0-dev.20250116) + version: 13.0.0(typescript@5.8.0-dev.20250128) tsconfig: specifier: workspace:* version: link:packages/tsconfig @@ -2508,7 +2508,7 @@ importers: version: 1.9.0 '@opentelemetry/auto-instrumentations-node': specifier: 0.55.2 - version: 0.55.2(@opentelemetry/api@1.9.0)(encoding@0.1.13) + version: 0.55.2(@opentelemetry/api@1.9.0) '@opentelemetry/context-async-hooks': specifier: 1.30.1 version: 1.30.1(@opentelemetry/api@1.9.0) @@ -2751,6 +2751,9 @@ importers: express-validator: specifier: ^7.1.0 version: 7.2.1 + fetch-retry: + specifier: ^5.0.6 + version: 5.0.6 knex: specifier: 3.1.0 version: 3.1.0(mysql2@3.12.0) @@ -14196,8 +14199,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@5.8.0-dev.20250116: - resolution: {integrity: sha512-co261sVUo0cZWvSWU8O2sx03ZfSYSqGcjxkxG5UDImcQ1ZCBLCsvrKrSpsdNvz0Q3hhNJY9to3fTTxIUWuvaTg==} + typescript@5.8.0-dev.20250128: + resolution: {integrity: sha512-zxnu5BSmL1qbW+jJyVvkdvIHog6eBy84HKlTJQDV97fqrbPKlS5WJVGj/1xKlbBMA9lyqYsp/ijkuJbaq4mt2g==} engines: {node: '>=14.17'} hasBin: true @@ -16512,11 +16515,11 @@ snapshots: dependencies: commander: 12.1.0 - '@commitlint/cli@19.6.1(@types/node@22.10.7)(typescript@5.8.0-dev.20250116)': + '@commitlint/cli@19.6.1(@types/node@22.10.7)(typescript@5.8.0-dev.20250128)': dependencies: '@commitlint/format': 19.5.0 '@commitlint/lint': 19.6.0 - '@commitlint/load': 19.6.1(@types/node@22.10.7)(typescript@5.8.0-dev.20250116) + '@commitlint/load': 19.6.1(@types/node@22.10.7)(typescript@5.8.0-dev.20250128) '@commitlint/read': 19.5.0 '@commitlint/types': 19.5.0 tinyexec: 0.3.2 @@ -16563,15 +16566,15 @@ snapshots: '@commitlint/rules': 19.6.0 '@commitlint/types': 19.5.0 - '@commitlint/load@19.6.1(@types/node@22.10.7)(typescript@5.8.0-dev.20250116)': + '@commitlint/load@19.6.1(@types/node@22.10.7)(typescript@5.8.0-dev.20250128)': dependencies: '@commitlint/config-validator': 19.5.0 '@commitlint/execute-rule': 19.5.0 '@commitlint/resolve-extends': 19.5.0 '@commitlint/types': 19.5.0 chalk: 5.4.1 - cosmiconfig: 9.0.0(typescript@5.8.0-dev.20250116) - cosmiconfig-typescript-loader: 6.1.0(@types/node@22.10.7)(cosmiconfig@9.0.0(typescript@5.8.0-dev.20250116))(typescript@5.8.0-dev.20250116) + cosmiconfig: 9.0.0(typescript@5.8.0-dev.20250128) + cosmiconfig-typescript-loader: 6.1.0(@types/node@22.10.7)(cosmiconfig@9.0.0(typescript@5.8.0-dev.20250128))(typescript@5.8.0-dev.20250128) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -17969,7 +17972,7 @@ snapshots: '@opentelemetry/api@1.9.0': {} - '@opentelemetry/auto-instrumentations-node@0.55.2(@opentelemetry/api@1.9.0)(encoding@0.1.13)': + '@opentelemetry/auto-instrumentations-node@0.55.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.1(@opentelemetry/api@1.9.0) @@ -18016,7 +18019,7 @@ snapshots: '@opentelemetry/resource-detector-aws': 1.10.0(@opentelemetry/api@1.9.0) '@opentelemetry/resource-detector-azure': 0.5.0(@opentelemetry/api@1.9.0) '@opentelemetry/resource-detector-container': 0.5.3(@opentelemetry/api@1.9.0) - '@opentelemetry/resource-detector-gcp': 0.32.0(@opentelemetry/api@1.9.0)(encoding@0.1.13) + '@opentelemetry/resource-detector-gcp': 0.32.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-node': 0.57.1(@opentelemetry/api@1.9.0) transitivePeerDependencies: @@ -18828,7 +18831,7 @@ snapshots: '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/resource-detector-gcp@0.32.0(@opentelemetry/api@1.9.0)(encoding@0.1.13)': + '@opentelemetry/resource-detector-gcp@0.32.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) @@ -21585,12 +21588,12 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 - cosmiconfig-typescript-loader@6.1.0(@types/node@22.10.7)(cosmiconfig@9.0.0(typescript@5.8.0-dev.20250116))(typescript@5.8.0-dev.20250116): + cosmiconfig-typescript-loader@6.1.0(@types/node@22.10.7)(cosmiconfig@9.0.0(typescript@5.8.0-dev.20250128))(typescript@5.8.0-dev.20250128): dependencies: '@types/node': 22.10.7 - cosmiconfig: 9.0.0(typescript@5.8.0-dev.20250116) + cosmiconfig: 9.0.0(typescript@5.8.0-dev.20250128) jiti: 2.4.2 - typescript: 5.8.0-dev.20250116 + typescript: 5.8.0-dev.20250128 cosmiconfig@8.3.6(typescript@5.7.3): dependencies: @@ -21610,14 +21613,14 @@ snapshots: optionalDependencies: typescript: 5.7.3 - cosmiconfig@9.0.0(typescript@5.8.0-dev.20250116): + cosmiconfig@9.0.0(typescript@5.8.0-dev.20250128): dependencies: env-paths: 2.2.1 import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 5.8.0-dev.20250116 + typescript: 5.8.0-dev.20250128 cpu-features@0.0.2: dependencies: @@ -21953,7 +21956,7 @@ snapshots: dependencies: semver: 7.6.3 shelljs: 0.8.5 - typescript: 5.8.0-dev.20250116 + typescript: 5.8.0-dev.20250128 dreamopt@0.8.0: dependencies: @@ -26763,13 +26766,13 @@ snapshots: '@pkgr/core': 0.1.1 tslib: 2.8.1 - syncpack@13.0.0(typescript@5.8.0-dev.20250116): + syncpack@13.0.0(typescript@5.8.0-dev.20250128): dependencies: '@effect/schema': 0.71.1(effect@3.6.5) chalk: 5.3.0 chalk-template: 1.1.0 commander: 12.1.0 - cosmiconfig: 9.0.0(typescript@5.8.0-dev.20250116) + cosmiconfig: 9.0.0(typescript@5.8.0-dev.20250128) effect: 3.6.5 enquirer: 2.4.1 fast-check: 3.21.0 @@ -27144,7 +27147,7 @@ snapshots: typescript@5.7.3: {} - typescript@5.8.0-dev.20250116: {} + typescript@5.8.0-dev.20250128: {} ua-parser-js@1.0.40: {} diff --git a/servers/account-data-deleter/package.json b/servers/account-data-deleter/package.json index 76e29e75e..f9f823eaa 100644 --- a/servers/account-data-deleter/package.json +++ b/servers/account-data-deleter/package.json @@ -40,6 +40,7 @@ "csv-stringify": "^6.5.1", "express": "4.21.2", "express-validator": "^7.1.0", + "fetch-retry": "^5.0.6", "knex": "3.1.0", "lodash": "4.17.21", "mysql2": "3.12.0", diff --git a/servers/account-data-deleter/src/config/index.ts b/servers/account-data-deleter/src/config/index.ts index 50ca1603a..8e36cd028 100644 --- a/servers/account-data-deleter/src/config/index.ts +++ b/servers/account-data-deleter/src/config/index.ts @@ -79,6 +79,12 @@ export const config = { apiVersion: '2024-06-20' as const, productId: 7, }, + fxa: { + clientId: process.env.FXA_CLIENT_ID || 'somefakeclientid', + secret: process.env.FXA_CLIENT_SECRET || 'somefakesecret', + oauthEndpoint: process.env.FXA_OAUTH_URL || 'https://localhost/', + version: 'v1', + }, database: { // contains tables for user, list, tags, annotations, etc. read: { diff --git a/servers/account-data-deleter/src/dataService/FxaRevoker.spec.ts b/servers/account-data-deleter/src/dataService/FxaRevoker.spec.ts new file mode 100644 index 000000000..14315a267 --- /dev/null +++ b/servers/account-data-deleter/src/dataService/FxaRevoker.spec.ts @@ -0,0 +1,52 @@ +import { FxaRevoker } from './FxaRevoker'; + +describe('FxARevoker', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + describe('with an access token', () => { + beforeEach(() => { + jest + .spyOn(FxaRevoker.prototype, 'fetchAccessToken') + .mockResolvedValue('accessme123'); + }); + it('returns false if revoking throws an error', async () => { + jest + .spyOn(FxaRevoker.prototype, 'requestRevokeToken') + .mockRejectedValueOnce(new Error('error fetching')); + const res = await new FxaRevoker('abc123').revokeToken(); + expect(res).toBeFalse(); + }); + it('returns false if revoking response is not ok', async () => { + jest + .spyOn(FxaRevoker.prototype, 'requestRevokeToken') + .mockResolvedValueOnce(new Response(null, { status: 500 })); + const res = await new FxaRevoker('abc123').revokeToken(); + expect(res).toBeFalse(); + }); + it('returns false if db delete method throws error', async () => { + jest + .spyOn(FxaRevoker.prototype, 'deleteAuthRecord') + .mockRejectedValueOnce(new Error('error deleting')); + const res = await new FxaRevoker('abc123').revokeToken(); + expect(res).toBeFalse(); + }); + it('returns true if process does not error', async () => { + jest + .spyOn(FxaRevoker.prototype, 'requestRevokeToken') + .mockResolvedValueOnce(new Response(null, { status: 200 })); + jest + .spyOn(FxaRevoker.prototype, 'deleteAuthRecord') + .mockResolvedValueOnce(1); + const res = await new FxaRevoker('abc123').revokeToken(); + expect(res).toBeTrue(); + }); + }); + it('returns true if no FxA tokens exist', async () => { + jest + .spyOn(FxaRevoker.prototype, 'fetchAccessToken') + .mockResolvedValueOnce(undefined); + const res = await new FxaRevoker('abc123').revokeToken(); + expect(res).toBeTrue(); + }); +}); diff --git a/servers/account-data-deleter/src/dataService/FxaRevoker.ts b/servers/account-data-deleter/src/dataService/FxaRevoker.ts new file mode 100644 index 000000000..adf411328 --- /dev/null +++ b/servers/account-data-deleter/src/dataService/FxaRevoker.ts @@ -0,0 +1,109 @@ +import { Knex } from 'knex'; +import { readClient, writeClient } from './clients'; +import * as Sentry from '@sentry/node'; +import { serverLogger } from '@pocket-tools/ts-logger'; +import fetchRetry from 'fetch-retry'; +import { config } from '../config'; +const newFetch = fetchRetry(fetch); + +export class FxaRevoker { + private write: Knex; + private read: Knex; + constructor(private readonly userId: string) { + this.write = writeClient(); + this.read = readClient(); + } + + /** + * Get the current FxA access token for a user, to be + * revoked during the account deletion process. We delete + * all records of auth creds in the account deletion process. + * This will notify Mozilla Accounts to remove Pocket from + * the integrated apps, so it no longer shows up on the + * account overview page. + */ + async fetchAccessToken(): Promise { + const token = await this.read('user_firefox_account') + .where({ + user_id: this.userId, + }) + .pluck('firefox_access_token'); + // The length assertion is technically unnecessary since + // user_id is the PK on this table + if (token != null && token.length === 1) { + return token[0]; + } else { + return undefined; + } + } + + /** + * Revoke an FxA access token, removing Pocket from integrated + * apps in Mozilla account, and delete the database record + * associated with Mozilla auth. + * @returns true if successful, false otherwise + */ + async revokeToken(): Promise { + try { + const token = await this.fetchAccessToken(); + // No FxA account data exists (old, unmigrated accounts) + if (token == null) { + return true; + } else { + const res = await this.requestRevokeToken(token); + if (!res.ok) { + throw new Error( + `Failed to revoke FxA access token [${res.status} - ${res.statusText}]`, + ); + } + await this.deleteAuthRecord(); + return true; + } + } catch (error) { + serverLogger.error({ + message: 'Failed to revoke and/or delete FxA access token', + errorData: error, + errorMessage: error.message, + userId: this.userId, + }); + Sentry.captureException(error); + return false; + } + } + + /** + * Make a request to the oauth endpoint to revoke FxA access token + * @param token the token to revoke + * @returns + */ + async requestRevokeToken(token: string): Promise { + const body = { + client_id: config.fxa.clientId, + client_secret: config.fxa.secret, + token, + token_type_hint: 'access_token', + }; + const fetchPath = `${config.fxa.oauthEndpoint}${config.fxa.version}/oauth/destroy`; + return newFetch(fetchPath, { + retryOn: [500, 502, 503], + retryDelay: (attempt, error, response) => { + return Math.pow(2, attempt) * 500; + }, + retries: 3, + method: 'post', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); + } + + /** + * Delete the auth record in Pocket's DB + */ + async deleteAuthRecord(): Promise { + return await this.write('user_firefox_account') + .where({ user_id: this.userId }) + .del(); + } +} diff --git a/servers/account-data-deleter/src/routes/index.ts b/servers/account-data-deleter/src/routes/index.ts index 6d173b312..6cf74c006 100644 --- a/servers/account-data-deleter/src/routes/index.ts +++ b/servers/account-data-deleter/src/routes/index.ts @@ -1,2 +1,3 @@ export { default as queueDeleteRouter } from './queueDelete'; export { default as stripeDeleteRouter } from './stripeDelete'; +export { default as revokeFxaRouter } from './revokeFxa'; diff --git a/servers/account-data-deleter/src/routes/revokeFxa.ts b/servers/account-data-deleter/src/routes/revokeFxa.ts new file mode 100644 index 000000000..0cd0cf18a --- /dev/null +++ b/servers/account-data-deleter/src/routes/revokeFxa.ts @@ -0,0 +1,33 @@ +import { Request, Response, Router } from 'express'; +import { checkSchema } from 'express-validator'; +import { validate } from './validator'; +import { nanoid } from 'nanoid'; +import { accountDeleteSchema } from './schemas'; +import { FxaRevoker } from '../dataService/FxaRevoker'; + +const router = Router(); + +/** + * This endpoint is called by the lambda that consumes account delete + * events forwarded from the event bus. When a user deletes their account, + * revoke any FxA access tokens and delete all FxA records in Pocket. + */ +router.post( + '/', + checkSchema(accountDeleteSchema), + validate, + async (req: Request, res: Response) => { + const requestId = req.body.traceId ?? nanoid(); + new FxaRevoker(req.body.userId).revokeToken(); + // Error handling and logging is done asynchronously; just respond OK + // if we received the message and kicked off the work + return res.send({ + status: 'OK', + message: `received message body ${JSON.stringify( + req.body, + )} (requestId='${requestId}')`, + }); + }, +); + +export default router; diff --git a/servers/account-data-deleter/src/server.ts b/servers/account-data-deleter/src/server.ts index ac6c6bf4f..b78830041 100644 --- a/servers/account-data-deleter/src/server.ts +++ b/servers/account-data-deleter/src/server.ts @@ -1,5 +1,9 @@ import express, { Application, json } from 'express'; -import { queueDeleteRouter, stripeDeleteRouter } from './routes'; +import { + queueDeleteRouter, + stripeDeleteRouter, + revokeFxaRouter, +} from './routes'; import { EventEmitter } from 'events'; import { BatchDeleteHandler, @@ -36,6 +40,7 @@ export async function startServer(port: number): Promise<{ }); app.use('/queueDelete', queueDeleteRouter); app.use('/stripeDelete', stripeDeleteRouter); + app.use('/revokeFxa', revokeFxaRouter); Sentry.setupExpressErrorHandler(app); diff --git a/servers/user-api/src/config/index.ts b/servers/user-api/src/config/index.ts index 35c694aee..1192a66d6 100644 --- a/servers/user-api/src/config/index.ts +++ b/servers/user-api/src/config/index.ts @@ -7,7 +7,6 @@ const tables = { tablesWithSensitivePii: [ 'users', 'user_google_account', - 'user_firefox_account', 'user_twitter_auth', 'users_social_tokens', 'users_tokens', @@ -72,6 +71,11 @@ export default { user_id: [...tables.tablesWithSensitivePii], created_by_user_id: ['channels'], }, + // For test seeds + userPIITestSeedTables: { + user_id: [...tables.tablesWithSensitivePii, 'user_firefox_account'], + created_by_user_id: ['channels'], + }, }, sentry: { dsn: process.env.SENTRY_DSN || '', diff --git a/servers/user-api/src/test/functional/seeds.ts b/servers/user-api/src/test/functional/seeds.ts index cffeed9cb..0a7d201dd 100644 --- a/servers/user-api/src/test/functional/seeds.ts +++ b/servers/user-api/src/test/functional/seeds.ts @@ -4,9 +4,9 @@ import { writeClient as conn } from '../../database/client'; import config from '../../config'; export async function truncatePiiTables() { - const allTables = Object.entries(config.database.userPIITables).flatMap( - ([_, tables]) => tables, - ); + const allTables = Object.entries( + config.database.userPIITestSeedTables, + ).flatMap(([_, tables]) => tables); await Promise.all( allTables.map((table: string) => { return conn()(table).truncate(); @@ -26,15 +26,17 @@ export async function PiiTableSeed(user_id: number, firefox_uid: string) { }; await Promise.all( - Object.entries(config.database.userPIITables).flatMap(([key, tables]) => { - return tables.map((tableName) => { - if (!(tableName in specialTablesSeeds)) { - return conn()(tableName).insert({ [key]: user_id }); - } else { - return conn()(tableName).insert(specialTablesSeeds[tableName]); - } - }); - }), + Object.entries(config.database.userPIITestSeedTables).flatMap( + ([key, tables]) => { + return tables.map((tableName) => { + if (!(tableName in specialTablesSeeds)) { + return conn()(tableName).insert({ [key]: user_id }); + } else { + return conn()(tableName).insert(specialTablesSeeds[tableName]); + } + }); + }, + ), ); }