Skip to content

Keep madmin from writing blank shopkeeper tokens - #158

Merged
dadachi merged 2 commits into
mainfrom
fix/madmin-blank-tokens
Oct 16, 2026
Merged

dadachi merged 2 commits into
mainfrom
fix/madmin-blank-tokens

Conversation

@dadachi

@dadachi dadachi commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Port of myturntagapi #218 and #219. There, saving a shopkeeper in /madmin returned 500 in production: PG::UniqueViolation on index_shopkeepers_on_reset_password_token, "Key (reset_password_token)=() already exists". madmin's edit form submits every field, so empty token fields arrive as ""; once one shopkeeper had "" stored, the next save of a shopkeeper with no token collided with it. The template has the same setup (ShopkeeperResource exposed reset_password_token and confirmation_token in the form, both with unique indexes).

  • ShopkeeperResource: reset_password_token and confirmation_token are form: false (still shown read-only on the show page), so madmin never writes "" into them
  • Migration 20261017000000_nullify_blank_shopkeeper_tokens clears "" already stored
  • MadminTest "saves the shopkeeper edit form as the browser submits it": GETs the edit page, parses the form with Nokogiri and PATCHes every field like a browser, while another shopkeeper holds "" tokens
  • ShopkeeperTest "a blank token finds no shopkeeper": where(reset_password_token: "") / where(confirmation_token: "") match nothing and with_reset_password_token("") is nil

The first commit normalized blank tokens to NULL in Shopkeeper (normalizes ... token.presence). The second commit removes that: Rails also normalizes finder values, so where(reset_password_token: "") became IS NULL and matched every shopkeeper without a token. A comment in Shopkeeper explains why the token columns are not normalized.

Other unique string columns exposed in madmin were checked: accounts_invitations.token, roles.tag, permissions.tag, admin_users.email, action_push_native_devices.token etc. are all NOT NULL and always filled, so blank-vs-NULL cannot collide there; only the shopkeeper tokens are nullable.

Test evidence

  • Madmin form test, before any fix: errors with PG::UniqueViolation ... index_shopkeepers_on_confirmation_token ... Key (confirmation_token)=() already exists. With the token fields put back in the form (no normalization), it errors the same way again
  • ShopkeeperTest "a blank token finds no shopkeeper" with the normalization in place: fails (Expected true to be nil or false, the where(reset_password_token: "").exists? assertion)
  • After the fix: shopkeeper_test.rb + madmin_test.rb 34 runs, 0 failures
  • bin/rubocop -f github clean; bin/ci passed (626 runs, 0 failures, 0 errors, 1 skip)

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y3Tt4YrVgQRxh5MCpiiric

dadachi and others added 2 commits October 17, 2026 08:06
Ported from myturntagapi, where saving a shopkeeper in madmin returned
500 in production: PG::UniqueViolation on
index_shopkeepers_on_reset_password_token, "Key (reset_password_token)=()
already exists". The edit form sends every field, so an empty token
arrives as "", and once one shopkeeper had "" stored, the next one with
no token collided with it. The template has the same form fields and
unique indexes.

Shopkeeper normalizes blank reset_password_token and confirmation_token
(both unique) to NULL, and a migration clears the "" already stored.
The madmin test submits the edit form with every field, as a browser
does, while another shopkeeper holds "" tokens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y3Tt4YrVgQRxh5MCpiiric
The previous commit normalized blank reset_password_token and
confirmation_token to NULL. Normalization also rewrites finder values,
so where(reset_password_token: "") became WHERE reset_password_token IS
NULL and matched every shopkeeper without a token. Ported from
myturntagapi #219.

Drop the normalization and keep the two tokens out of madmin's
shopkeeper form (form: false), the only writer that sent "". The
migration that clears stored "" values stays.

ShopkeeperTest checks that a blank token finds no shopkeeper (it failed
with the normalization); the madmin form test still fails with the
token fields back in the form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y3Tt4YrVgQRxh5MCpiiric
@dadachi dadachi changed the title Store blank shopkeeper tokens as NULL so madmin can save shopkeepers Keep madmin from writing blank shopkeeper tokens Oct 16, 2026
@dadachi
dadachi merged commit d731bb6 into main Oct 16, 2026
3 checks passed
@dadachi
dadachi deleted the fix/madmin-blank-tokens branch October 16, 2026 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant