A Drupal module, as a game

Shy One-Time
Defend the Inbox

Mail scanners keep clicking your users' password-reset links before they do. Install the module, read the log and keep the bots away from the link.

Derived from the module's agent docs · WASD / arrows to move · Space to shove · mouse for everything else
The problem

“But I never used that link!”

01

Drupal sends a one-time link

Your user requests a new password. Drupal emails a single-use link.

/user/reset/1/1728…/Xf3…
02

A scanner clicks first

A mail-security gateway, link-preview service or crawler fetches the URL in transit.

GET /user/reset/… → token spent
03

Then the human clicks

Your user opens the email and clicks the link, seconds later.

04

…and gets this

You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.

Shy One-Time intercepts requests to user.reset and user.reset.login, so bots never spend the token. Crawlers get a 403 and the user agents you list get a 302 to the login page.

Level 1

Set it up

my-drupal-site
STEP 1 / 3

You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.
WASD/arrows move · Space shove · click a log row → Save

A real user was locked out

Your pattern also matches a real browser. That user is redirected to /user/login every time, so they can never complete their password reset.

Patterns are case-insensitive substring matches. Keep them narrow and specific: copy the scanner's full user agent from the log. And remember it's a trade, not a fix: the user agent is a header anyone can fake.

Level 2

Defend the inbox

Steer the shy guard with WASD / arrows and press Space to shove.

■ Known crawlers bounce off the shield by themselves (403).
■ Unknown scanners get through: shove them away, then click their row in the log and press Save, and they get a 302.
■ Humans must reach the link.

Inbox defended

Nice work, shy guard.

1 · The problem

Mail scanners, preview bots and crawlers open one-time links before your users, so Drupal says the link “has either been used or is no longer valid”.

2 · Install

composer require drupal/shy_one_time → drush en shy_one_time -y. Known crawlers are blocked out of the box (CrawlerDetect → 403).

3 · Unknown scanners

Copy their user agent from the dblog into /admin/config/system/shy_one_time, one per line. Matches get a 302 to /user/login and the token survives.

4 · Keep patterns narrow

Matching is a case-insensitive substring. A pattern like “Mozilla” matches real browsers, and those users can never reset their password.

5 · A trade, not a fix

A link that survives a third-party fetch can still be used by that third party, and the user agent is a header anyone can fake. Single-use links exist to limit the damage when a reset URL leaks.

Source: shy_one_time/2.0.x/agent/start.md + usage.md