feat(payments-api): Delete glean/nimbus data on account deletion#20381
feat(payments-api): Delete glean/nimbus data on account deletion#20381david1alvarez wants to merge 1 commit intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds account-deletion cleanup hooks for Payments telemetry by generating all historical Nimbus user IDs and emitting deletion log records when FxA sends a delete-user webhook event, enabling downstream deletion of associated Glean/Nimbus-subplat data.
Changes:
- Wire
PaymentsGleanService.handleUserDelete()into the FxAdelete-userwebhook handler. - Add deletion-time Nimbus ID generation across historical namespaces (
deletionNamespaces) and log one deletion record per generated Nimbus ID. - Update tests and sample env files to include the new Nimbus manager config field.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| libs/payments/webhooks/src/lib/fxa-webhooks.service.ts | Calls glean deletion handler during FxA delete-user webhook processing. |
| libs/payments/webhooks/src/lib/fxa-webhooks.service.spec.ts | Mocks PaymentsGleanService and asserts delete-user triggers handleUserDelete. |
| libs/payments/webhooks/src/lib/fxa-webhooks.controller.spec.ts | Adds PaymentsGleanService provider for controller test module setup. |
| libs/payments/metrics/src/lib/glean/glean.service.ts | Adds handleUserDelete to log deletion events per Nimbus ID. |
| libs/payments/metrics/src/lib/glean/glean.service.spec.ts | Adds unit tests for handleUserDelete logging behavior. |
| libs/payments/experiments/src/lib/nimbus.manager.ts | Adds generateAllNimbusIdsForDeletion using deletionNamespaces. |
| libs/payments/experiments/src/lib/nimbus.manager.spec.ts | Adds tests for generateAllNimbusIdsForDeletion behavior and fallback. |
| libs/payments/experiments/src/lib/nimbus.manager.config.ts | Adds deletionNamespaces config parsing/validation. |
| apps/payments/next/.env | Adds NIMBUS_MANAGER__DELETION_NAMESPACES sample value. |
| apps/payments/api/.env | Adds Nimbus manager config section (currently empty values). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @Transform(({ value }) => | ||
| typeof value === 'string' ? value.split(',') : value | ||
| ) |
There was a problem hiding this comment.
deletionNamespaces is populated via value.split(','), which turns an empty env var into [""] and keeps whitespace (e.g. "uuid1, uuid2"). Downstream this is used as a UUID namespace for uuidv5, so empty strings / whitespace will cause runtime errors. Consider trimming and filtering falsy entries, and returning undefined when the resulting list is empty (so code can fall back to namespace).
| @Transform(({ value }) => | |
| typeof value === 'string' ? value.split(',') : value | |
| ) | |
| @Transform(({ value }) => { | |
| if (typeof value !== 'string') { | |
| return value; | |
| } | |
| const deletionNamespaces = value | |
| .split(',') | |
| .map((namespace) => namespace.trim()) | |
| .filter(Boolean); | |
| return deletionNamespaces.length > 0 ? deletionNamespaces : undefined; | |
| }) |
| const namespaces = | ||
| this.nimbusManagerConfig.deletionNamespaces ?? | ||
| [this.nimbusManagerConfig.namespace]; | ||
| return namespaces.map((ns) => generateNimbusId(ns, fxaUid)); |
There was a problem hiding this comment.
generateAllNimbusIdsForDeletion uses deletionNamespaces ?? [namespace], which will not fall back when deletionNamespaces is an empty array (or contains only empty strings after parsing). That can silently produce no IDs (no deletion logs) or can throw if a namespace is invalid. Consider normalizing with filter(Boolean) and falling back when the normalized list is empty; also consider always including this.nimbusManagerConfig.namespace and de-duplicating to reduce misconfiguration risk (missing current namespace would skip deleting the current nimbus id).
| const namespaces = | |
| this.nimbusManagerConfig.deletionNamespaces ?? | |
| [this.nimbusManagerConfig.namespace]; | |
| return namespaces.map((ns) => generateNimbusId(ns, fxaUid)); | |
| const configuredNamespaces = ( | |
| this.nimbusManagerConfig.deletionNamespaces ?? [] | |
| ).filter(Boolean); | |
| const namespaces = [ | |
| ...new Set([ | |
| ...configuredNamespaces, | |
| this.nimbusManagerConfig.namespace, | |
| ]), | |
| ]; | |
| return (namespaces.length > 0 | |
| ? namespaces | |
| : [this.nimbusManagerConfig.namespace] | |
| ).map((ns) => generateNimbusId(ns, fxaUid)); |
| const nimbusUserIds = | ||
| this.nimbusManager.generateAllNimbusIdsForDeletion(uid); | ||
| for (const nimbusUserId of nimbusUserIds) { | ||
| // PAY-3438: Log format consumed by BigQuery ETL for Glean Shredder. | ||
| // Do not change jsonPayload.type or field names without updating the ETL query. | ||
| this.log.log('glean.user.delete', { | ||
| uid, | ||
| nimbus_user_id: nimbusUserId, | ||
| }); |
There was a problem hiding this comment.
handleUserDelete can throw if any namespace used to generate the nimbus id is missing/invalid (e.g., empty NIMBUS_MANAGER__DELETION_NAMESPACES parses into [""] and uuidv5 will error). Since this runs inside the FxA delete-user webhook handler, that would fail the webhook request and prevent emitting deletion logs. Consider validating/normalizing namespaces (or catching generation errors) so this path can’t take down webhook handling.
| const nimbusUserIds = | |
| this.nimbusManager.generateAllNimbusIdsForDeletion(uid); | |
| for (const nimbusUserId of nimbusUserIds) { | |
| // PAY-3438: Log format consumed by BigQuery ETL for Glean Shredder. | |
| // Do not change jsonPayload.type or field names without updating the ETL query. | |
| this.log.log('glean.user.delete', { | |
| uid, | |
| nimbus_user_id: nimbusUserId, | |
| }); | |
| try { | |
| const nimbusUserIds = | |
| this.nimbusManager.generateAllNimbusIdsForDeletion(uid); | |
| for (const nimbusUserId of nimbusUserIds) { | |
| // PAY-3438: Log format consumed by BigQuery ETL for Glean Shredder. | |
| // Do not change jsonPayload.type or field names without updating the ETL query. | |
| this.log.log('glean.user.delete', { | |
| uid, | |
| nimbus_user_id: nimbusUserId, | |
| }); | |
| } | |
| } catch (error) { | |
| this.log.error( | |
| `Failed to generate Nimbus deletion ids for uid ${uid}`, | |
| error instanceof Error ? error.stack : undefined | |
| ); |
| NIMBUS_MANAGER__NAMESPACE= | ||
| NIMBUS_MANAGER__DELETION_NAMESPACES= |
There was a problem hiding this comment.
NIMBUS_MANAGER__NAMESPACE and NIMBUS_MANAGER__DELETION_NAMESPACES are set to empty values here. With the current parsing (split(',')), an empty DELETION_NAMESPACES becomes [""], and the delete-user webhook path will attempt to generate a UUID v5 with an invalid namespace, throwing at runtime. Use a valid UUID for NIMBUS_MANAGER__NAMESPACE (even if ENABLED=false), and either omit DELETION_NAMESPACES or set it to a valid comma-separated UUID list.
| NIMBUS_MANAGER__NAMESPACE= | |
| NIMBUS_MANAGER__DELETION_NAMESPACES= | |
| NIMBUS_MANAGER__NAMESPACE=6ba7b810-9dad-11d1-80b4-00c04fd430c8 | |
| NIMBUS_MANAGER__DELETION_NAMESPACES=6ba7b810-9dad-11d1-80b4-00c04fd430c8 |
| // PAY-3438: Log format consumed by BigQuery ETL for Glean Shredder. | ||
| // Do not change jsonPayload.type or field names without updating the ETL query. | ||
| this.log.log('glean.user.delete', { | ||
| uid, | ||
| nimbus_user_id: nimbusUserId, | ||
| }); | ||
| } |
There was a problem hiding this comment.
The comment says the ETL depends on jsonPayload.type and field names, but this call uses Nest's Logger.log(message, context?) signature where the second argument is typically a context string. If the runtime logger isn’t a winston-compatible structured logger, the { uid, nimbus_user_id } object may be dropped or coerced to "[object Object]", breaking the BigQuery shredder. Consider logging a single structured object (including an explicit type field) via the winston/shared logger provider used in production logging.
Because: * When an account is deleted, subplat data associated with the user (including nimbus-associated data) needs to be removed as well This commit: * Adds in a record-keeping deletionNamespaces field to the nimbus config, for ensuring deletion of data associated with historical nimbus UUID generations * Logs a record via the winston logger in a format designed to be intercepted by a bigquery ETL query to delete glean data with a matching nimbus user id * Wires in the deletion logger method into the fxa webhook call for the delete-user fxa events Closes #PAY-3438
Because: * Current glean deletion ETL query duplicates the nimbus_user_id derivation logic that lives in the fxa repository This commit: * Updates the script to match the paired [PR](mozilla/fxa#20381), while still supporting the existing approach Closes #PAY-3438
Because:
This commit:
Closes #PAY-3438
Checklist
Put an
xin the boxes that apply