From c8bc40146afccc957478d1520b1ee3c464cac67e Mon Sep 17 00:00:00 2001 From: Jaanus Sellin Date: Tue, 12 Nov 2024 13:28:19 +0200 Subject: [PATCH] feat: email will be stored hashed now for all users (#8720) Adding email_hash column to users table. We will update all existing users to have hashed email. All new users will also get the hash. We are fine to use md5, because we just need uniqueness. We have emails in events table stored anyways, so it is not sensitive. --- src/lib/db/user-store.ts | 9 +++++++- .../20241112113555-user-email-hash.js | 18 +++++++++++++++ src/test/e2e/api/admin/user-admin.e2e.test.ts | 23 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/migrations/20241112113555-user-email-hash.js diff --git a/src/lib/db/user-store.ts b/src/lib/db/user-store.ts index 18e5b43092..04d2ab73a6 100644 --- a/src/lib/db/user-store.ts +++ b/src/lib/db/user-store.ts @@ -108,8 +108,15 @@ class UserStore implements IUserStore { } async insert(user: ICreateUser): Promise { + const emailHash = user.email + ? this.db.raw('md5(?)', [user.email]) + : null; const rows = await this.db(TABLE) - .insert({ ...mapUserToColumns(user), created_at: new Date() }) + .insert({ + ...mapUserToColumns(user), + email_hash: emailHash, + created_at: new Date(), + }) .returning(USER_COLUMNS); return rowToUser(rows[0]); } diff --git a/src/migrations/20241112113555-user-email-hash.js b/src/migrations/20241112113555-user-email-hash.js new file mode 100644 index 0000000000..08d04517b8 --- /dev/null +++ b/src/migrations/20241112113555-user-email-hash.js @@ -0,0 +1,18 @@ +exports.up = (db, cb) => { + db.runSql(` + ALTER TABLE users + ADD COLUMN IF NOT EXISTS email_hash VARCHAR(32); + + UPDATE users + SET email_hash = md5(email::text); + `, cb); + +}; + +exports.down = (db, cb) => { + db.runSql(` + ALTER TABLE users + DROP COLUMN IF EXISTS email_hash; + `, cb); +}; + diff --git a/src/test/e2e/api/admin/user-admin.e2e.test.ts b/src/test/e2e/api/admin/user-admin.e2e.test.ts index df670db168..1467fdabb5 100644 --- a/src/test/e2e/api/admin/user-admin.e2e.test.ts +++ b/src/test/e2e/api/admin/user-admin.e2e.test.ts @@ -18,6 +18,7 @@ import { randomId } from '../../../../lib/util/random-id'; import { omitKeys } from '../../../../lib/util/omit-keys'; import type { ISessionStore } from '../../../../lib/types/stores/session-store'; import type { IUnleashStores } from '../../../../lib/types'; +import { createHash } from 'crypto'; let stores: IUnleashStores; let db: ITestDb; @@ -405,3 +406,25 @@ test('Anonymises name, username and email fields if anonymiseEventLog flag is se expect(body.users[0].name).toEqual('3a8b17647@unleash.run'); expect(body.users[0].username).toEqual(''); // Not set, so anonymise should return the empty string. }); + +test('creates user with email md5 hash', async () => { + await app.request + .post('/api/admin/user-admin') + .send({ + email: `hasher@getunleash.ai`, + name: `Some Name Hash`, + rootRole: editorRole.id, + }) + .set('Content-Type', 'application/json'); + + const user = await db + .rawDatabase('users') + .where({ email: 'hasher@getunleash.ai' }) + .first(['email_hash']); + + const expectedHash = createHash('md5') + .update('hasher@getunleash.ai') + .digest('hex'); + + expect(user.email_hash).toBe(expectedHash); +});