Эх сурвалжийг харах

Add AnalyticsManager function for registering email changes

GitOrigin-RevId: ad42703fdc186936866b44c47492e3f653658f4f
Andrew Rumble 1 жил өмнө
parent
commit
ae504e8af5

+ 57 - 0
services/web/app/src/Features/Analytics/AnalyticsManager.js

@@ -7,6 +7,7 @@ const crypto = require('crypto')
 const _ = require('lodash')
 const { expressify } = require('@overleaf/promise-utils')
 const logger = require('@overleaf/logger')
+const { createHash } = require('crypto')
 
 const analyticsEventsQueue = Queues.getQueue('analytics-events')
 const analyticsEditingSessionsQueue = Queues.getQueue(
@@ -18,6 +19,7 @@ const analyticsUserPropertiesQueue = Queues.getQueue(
 const analyticsAccountMappingQueue = Queues.getQueue(
   'analytics-account-mapping'
 )
+const analyticsEmailChangeQueue = Queues.getQueue('analytics-email-change')
 
 const ONE_MINUTE_MS = 60 * 1000
 
@@ -148,6 +150,7 @@ function setUserPropertyForSessionInBackground(session, property, value) {
 
 /**
  * @typedef {(import('./types').AccountMapping)} AccountMapping
+ * @typedef {(import('./types').EmailChangePayload)} EmailChangePayload
  */
 
 /**
@@ -193,6 +196,59 @@ function registerAccountMapping({
     })
 }
 
+/**
+ * Registers operations relating to email addresses.
+ *
+ * Generally, this should be called through the EmailChangeHelper module to ensure
+ * that the right data is sent.
+ *
+ * This will be called whenever an email is created, updated or deleted.
+ *
+ * @param {EmailChangePayload} payload
+ */
+function registerEmailChange({
+  userId,
+  email,
+  action,
+  createdAt = new Date(),
+  emailCreatedAt,
+  isPrimary,
+  emailConfirmedAt,
+  emailDeletedAt,
+}) {
+  Metrics.analyticsQueue.inc({ status: 'adding', event_type: 'email-change' })
+
+  const salt = Settings.analytics?.hashedEmailSalt ?? ''
+
+  const hashedEmail = createHash('sha256')
+    .update(`${email}${salt}`)
+    .digest('hex')
+
+  analyticsEmailChangeQueue
+    .add('email-change', {
+      action,
+      createdAt,
+      emailDeletedAt,
+      email: hashedEmail,
+      emailConfirmedAt,
+      emailCreatedAt,
+      isPrimary,
+      userId,
+    })
+    .then(() => {
+      Metrics.analyticsQueue.inc({
+        status: 'added',
+        event_type: 'email-change',
+      })
+    })
+    .catch(() => {
+      Metrics.analyticsQueue.inc({
+        status: 'error',
+        event_type: 'email-change',
+      })
+    })
+}
+
 function updateEditingSession(userId, projectId, countryCode, segmentation) {
   if (!userId) {
     return
@@ -400,5 +456,6 @@ module.exports = {
   updateEditingSession,
   getIdsFromSession,
   registerAccountMapping,
+  registerEmailChange,
   analyticsIdMiddleware: expressify(analyticsIdMiddleware),
 }

+ 11 - 0
services/web/app/src/Features/Analytics/types.d.ts

@@ -7,3 +7,14 @@ export type AccountMapping = {
   targetEntityId: string
   createdAt: string
 }
+
+export type EmailChangePayload = {
+  userId: string
+  email: string
+  isPrimary: boolean
+  action: 'created' | 'deleted' | 'updated'
+  createdAt: string
+  emailDeletedAt?: string | null
+  emailCreatedAt?: string | null
+  emailConfirmedAt?: string | null
+}

+ 25 - 1
services/web/test/unit/src/Analytics/AnalyticsManagerTests.js

@@ -16,7 +16,7 @@ describe('AnalyticsManager', function () {
     this.fakeUserId = 'dbfc9438d14996f73dd172fb'
     this.analyticsId = 'ecdb935a-52f3-4f91-aebc-7a70d2ffbb55'
     this.Settings = {
-      analytics: { enabled: true },
+      analytics: { enabled: true, hashedEmailSalt: 'salt' },
     }
     this.analyticsEventsQueue = {
       add: sinon.stub().resolves(),
@@ -307,6 +307,30 @@ describe('AnalyticsManager', function () {
         message
       )
     })
+
+    it('email change', async function () {
+      const message = {
+        userId: this.fakeUserId,
+        email: 'test@example.com',
+        createdAt: '2021-01-01T00:00:00Z',
+        action: 'created',
+        emailCreatedAt: '2021-01-01T00:00:00Z',
+        isPrimary: false,
+      }
+      this.AnalyticsManager.registerEmailChange(message)
+      const convertedMessage = {
+        ...message,
+        emailConfirmedAt: undefined,
+        emailDeletedAt: undefined,
+        email:
+          '1778d425d64c5259ef7b574a2488647eb51ca739a0b16bfa0e2e3e16fff362db', // sha256 hash of email + salt
+      }
+      sinon.assert.calledWithMatch(
+        this.analyticsEmailChangeQueue.add,
+        'email-change',
+        convertedMessage
+      )
+    })
   })
 
   describe('AnalyticsIdMiddleware', function () {