Skip to content

Periodic encryption of plaintext passwords in user_ table #35766

@wezell

Description

@wezell

Description

Add a scheduled job that periodically scans the user_ table for rows whose passwordEncrypted boolean is false (i.e., the password is sitting in password_ as plaintext) and rewrites them with a secure hash via PasswordFactoryProxy.generateHash, then flips passwordEncrypted = true.

Why this is needed

Plaintext passwords can land in the user_ table from several paths:

  • Legacy migration data and seed inserts (the bootstrap SQL contains passwordEncrypted = false rows by design).
  • Bulk user imports that bypass the standard UserAPI.save(..., validatePassword=true) path.
  • Manual SQL inserts during recovery or environment provisioning.
  • Older code paths that set the password but never set the encrypted flag.

A periodic sweep is a defense-in-depth control: it guarantees that no matter how a plaintext password lands in the column, it gets hashed on the next tick rather than persisting forever.

Behaviour

  • Runs every minute by default (configurable cron).
  • Implemented as a Quartz StatefulJob so concurrent firings cannot overlap.
  • Uses PasswordFactoryProxy.generateHash — the same hashing utility used everywhere else in the platform (authentication continues to work because authPassword accepts the resulting hash).
  • Skips rows where password_ is null.
  • Logs per-cycle counts (only when there is work to do — silent when the table is clean).

Configuration

Property Default Effect
ENABLE_ENCRYPT_PLAIN_PASSWORDS_JOB true Kill switch. Checked at startup (job is not scheduled if false) and at every firing (so it can be flipped at runtime without a restart).
ENCRYPT_PLAIN_PASSWORDS_CRON_EXPRESSION 0 0/1 * * * ? Standard Quartz cron expression.

Acceptance Criteria

  • EncryptPlainPasswordsJob exists in com.dotmarketing.quartz.job implementing StatefulJob.
  • Job is registered in DotInitScheduler and runs every minute by default.
  • Toggle via ENABLE_ENCRYPT_PLAIN_PASSWORDS_JOB works at both startup and runtime.
  • Integration tests cover: happy path hash + auth roundtrip, already-encrypted rows untouched, null password skipped, multiple rows in one pass, disabled-flag no-op.
  • No regression in existing user auth flows.

Additional Context

This is a backend-only change. No API contract, schema, or frontend touched.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    New

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions