1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-23 13:46:45 +02:00

chore: amend user-created-missing events (#10333)

## About the changes
Users could have been created in Unleash without a corresponding event
(a.k.a. audit log), due to a non transactional user insert
([fix](https://github.com/Unleash/unleash/pull/10327)). This could have
happened because of providing the wrong role id or some other causes
we're not aware of.

This amends the situation by inserting an event for each user that
exists in the instance (not deleted) and doesn't have it's corresponding
user-created event.

The event is inserted as already announced because this happened in the
past.

The event log will look like this (simulated the situation in local
dev):
```json
{
  "id": 11,
  "type": "user-created",
  "createdBy": "unleash_system_user",
  "createdAt": "2025-07-08T16:06:17.428Z",
  "createdByUserId": null,
  "data": {
    "id": "6",
    "email": "xyz@three.com"
  },
  "preData": null,
  "tags": [],
  "featureName": null,
  "project": null,
  "environment": null,
  "label": "User created",
  "summary": "**unleash_system_user** created user ****"
}
```

The main problem is we can't create the event in the past, so this will
have to do it
This commit is contained in:
Gastón Fournier 2025-07-09 09:19:25 +02:00 committed by GitHub
parent d7be47609d
commit 902845bf82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 110 additions and 0 deletions

View File

@ -0,0 +1,42 @@
exports.up = function(db, cb) {
db.runSql(`
INSERT INTO events (created_at, created_by, type, data, announced)
SELECT
u.created_at AS created_at,
'unleash_system_user' AS created_by,
'user-created' AS type,
json_build_object(
'id', u.id,
'name', u.name,
'email', u.email
)::jsonb AS data,
true AS announced
FROM users u
WHERE
is_system = false
AND is_service = false
AND deleted_at IS NULL
AND email IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM events
WHERE type = 'user-created'
AND (data ->> 'id')::int = u.id
LIMIT 1
)
ON CONFLICT DO NOTHING;
`, (err, results) => {
if (err) {
console.log('Error inserting user-created events:', err);
return cb(err);
}
if (results.rowCount){
console.log('Amended user-created event log. Number of new records:', results.rowCount);
}
cb();
});
};
exports.down = function(db, cb) {
cb();
// No down migration needed as this is a data backfill.
};

View File

@ -1,3 +1,5 @@
import postgresPkg from 'pg';
const { Client } = postgresPkg;
import {
type IUnleashTest,
setupAppWithCustomConfig,
@ -19,6 +21,11 @@ import { omitKeys } from '../../../../lib/util/omit-keys.js';
import type { ISessionStore } from '../../../../lib/types/stores/session-store.js';
import type { IUnleashStores } from '../../../../lib/types/index.js';
import { createHash } from 'crypto';
import { createDb } from '../../../../lib/db/db-pool.js';
import { migrateDb } from '../../../../migrator.js';
import { createTestConfig } from '../../../config/test-config.js';
import { v4 as uuidv4 } from 'uuid';
import { getDbConfig } from '../../../../lib/server-impl.js';
let stores: IUnleashStores;
let db: ITestDb;
@ -31,6 +38,67 @@ let sessionStore: ISessionStore;
let editorRole: IRole;
let adminRole: IRole;
describe('Users created without an event are amended', () => {
const testDbName = `migration_test_${uuidv4().replace(/-/g, '')}`;
const dbConnConfig = getDbConfig();
beforeAll(async () => {
// create a new empty database for this test
const client = new Client(dbConnConfig);
await client.connect();
await client.query(`CREATE DATABASE ${testDbName}`);
await client.end();
});
test('should amend users created without an event', async () => {
// connect to the new database
const config = createTestConfig({
db: {
...dbConnConfig,
ssl: false,
database: testDbName,
},
getLogger,
});
const db = createDb(config);
// migrate up to the migration we want to test
await migrateDb(config, '20250707153020-unknown-flags-environment.js');
const eventsBefore = await db('events').select('*');
const insertedUser = (
await db('users')
.insert({
created_at: new Date('2023-01-01T00:00:00Z'),
email: 'some@getunelash.io',
name: 'Some Name',
})
.returning('*')
)[0];
expect(insertedUser).toBeDefined();
expect(insertedUser.name).toBe('Some Name');
expect(insertedUser.created_at).toBeDefined();
const eventsAfter = await db('events').select('*');
expect(eventsAfter.length).toBe(eventsBefore.length);
// apply the rest of migrations
await migrateDb(config);
const eventsPostMigrations = await db('events').select('*');
expect(eventsPostMigrations.length).toBe(eventsBefore.length + 1);
const userCreatedEvent = eventsPostMigrations.find(
(e) => e.type === USER_CREATED && e.data.id === insertedUser.id,
);
expect(userCreatedEvent).toMatchObject({
type: 'user-created',
created_at: new Date('2023-01-01T00:00:00Z'),
data: {
id: insertedUser.id,
email: 'some@getunelash.io',
name: 'Some Name',
},
});
});
});
describe('User Admin API with email configuration', () => {
beforeAll(async () => {
db = await dbInit('user_admin_api_serial', getLogger);