From 66c9562298a0d95c33b3bcfb6395126b4d859399 Mon Sep 17 00:00:00 2001 From: Gaetan SENN Date: Tue, 16 Sep 2025 16:04:38 +0200 Subject: [PATCH] Add activity tracking for user suspension due to failed login attempts (#25836) * feat: log user suspension to activity when rate limit reached - Add AUTH_FAIL action to activity constants - Log to directus_activity table when user gets suspended after exceeding login attempt limit - Includes user ID, IP, user agent, origin and descriptive comment for audit trail - Only logs when accountability context is available (web requests) * feat: add github username to contributors for CLA * build: add changeset * fix: use UPDATE action * feat: remove @directus/constants scope * refactor: clean up code formating * feat: add revision for activity update * refactor: fix linter * fix: remove local db * fix: inject full user for revision and remove activity comment * Update .changeset/four-doodles-give.md --------- Co-authored-by: daedalus <44623501+ComfortablyCoding@users.noreply.github.com> --- .changeset/four-doodles-give.md | 5 +++++ api/src/services/authentication.ts | 23 +++++++++++++++++++++++ contributors.yml | 1 + 3 files changed, 29 insertions(+) create mode 100644 .changeset/four-doodles-give.md diff --git a/.changeset/four-doodles-give.md b/.changeset/four-doodles-give.md new file mode 100644 index 0000000000..15b2ce2e3a --- /dev/null +++ b/.changeset/four-doodles-give.md @@ -0,0 +1,5 @@ +--- +'@directus/api': patch +--- + +Added activity tracking for user suspension due to failed login attempts diff --git a/api/src/services/authentication.ts b/api/src/services/authentication.ts index f28a31795a..db0bdf2195 100644 --- a/api/src/services/authentication.ts +++ b/api/src/services/authentication.ts @@ -24,6 +24,7 @@ import { getMilliseconds } from '../utils/get-milliseconds.js'; import { getSecret } from '../utils/get-secret.js'; import { stall } from '../utils/stall.js'; import { ActivityService } from './activity.js'; +import { RevisionsService } from './revisions.js'; import { SettingsService } from './settings.js'; import { TFAService } from './tfa.js'; @@ -139,6 +140,28 @@ export class AuthenticationService { 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 { diff --git a/contributors.yml b/contributors.yml index 86063c9016..eb24fb6c7e 100644 --- a/contributors.yml +++ b/contributors.yml @@ -223,3 +223,4 @@ - jekuer - timio23 - khanahmad4527 +- gaetansenn