Repository: directus/directus
File: api/src/services/authentication.ts (TypeScript)

Code before:
			try {
				await loginAttemptsLimiter.consume(user.id);
			} catch (error) {
				if (error instanceof RateLimiterRes && error.remainingPoints === 0) {
					await this.knex('directus_users').update({ status: 'suspended' }).where({ id: user.id });
					user.status = 'suspended';

					if (this.accountability) {
						const activity = await this.activityService.createOne({
							action: Action.UPDATE,
							user: user.id,
							ip: this.accountability.ip,
							user_agent: this.accountability.userAgent,
							origin: this.accountability.origin,
							collection: 'directus_users',
							item: user.id,
							comment: `User suspended after ${allowedAttempts} failed attempts`,
						});

						const revisionsService = new RevisionsService({ knex: this.knex, schema: this.schema });

						await revisionsService.createOne({
							activity: activity,
							collection: 'directus_users',
							item: user.id,
							data: { status: 'suspended' },
							delta: { status: 'suspended' },
						});
					}

					// This means that new attempts after the user has been re-activated will be accepted
					await loginAttemptsLimiter.set(user.id, 0, 0);
				} else {
					throw new ServiceUnavailableError({
						service: 'authentication',
						reason: 'Rate limiter unreachable',
					});
				}
			}
		}

		try {
			await provider.login(clone(user), cloneDeep(updatedPayload));
		} catch (e) {
			emitStatus('fail');
			await stall(STALL_TIME, timeStart);
			throw e;
		}

		if (user.tfa_secret && !options?.otp) {
			emitStatus('fail');

Review comment (suggestion):
I believe the full user object should be added here, this will ensure the revision previews displays the correct data at the time of change. 

#### Before
- Assuming an email and password are set

<img width="857" height="823" alt="Screenshot 2025-09-15 at 7 09 28 PM" src="https://github.com/user-attachments/assets/d02aff38-a1ce-4d57-a243-4aca26c2e7de" />

#### After
- Assuming an email and password are set

<img width="857" height="823" alt="Screenshot 2025-09-15 at 7 09 37 PM" src="https://github.com/user-attachments/assets/9470b071-550d-4c8e-ae2d-e1eb97d82e8f" />

```suggestion
							data: user,
```

Code after:
			try {
				await loginAttemptsLimiter.consume(user.id);
			} catch (error) {
				if (error instanceof RateLimiterRes && error.remainingPoints === 0) {
					await this.knex('directus_users').update({ status: 'suspended' }).where({ id: user.id });
					user.status = 'suspended';

					if (this.accountability) {
						const activity = await this.activityService.createOne({
							action: Action.UPDATE,
							user: user.id,
							ip: this.accountability.ip,
							user_agent: this.accountability.userAgent,
							origin: this.accountability.origin,
							collection: 'directus_users',
							item: user.id,
						});

						const revisionsService = new RevisionsService({ knex: this.knex, schema: this.schema });

						await revisionsService.createOne({
							activity: activity,
							collection: 'directus_users',
							item: user.id,
							data: user,
							delta: { status: 'suspended' },
						});
					}

					// This means that new attempts after the user has been re-activated will be accepted
					await loginAttemptsLimiter.set(user.id, 0, 0);
				} else {
					throw new ServiceUnavailableError({
						service: 'authentication',
						reason: 'Rate limiter unreachable',
					});
				}
			}
		}

		try {
			await provider.login(clone(user), cloneDeep(updatedPayload));
		} catch (e) {
			emitStatus('fail');
			await stall(STALL_TIME, timeStart);
			throw e;
		}

		if (user.tfa_secret && !options?.otp) {
			emitStatus('fail');
			await stall(STALL_TIME, timeStart);