Page MenuHomePhabricator

CheckUser temporary account IP reveal does not work on Codex Special:Block
Closed, ResolvedPublicBUG REPORT

Description

What is the problem?

T&S Product recently added clickable link to reveal a temp user's IP address on Special:Block when the person browsing Special:Block has rights to CheckUser

This doesn't work in the Codex version of Special:Block, and gives this javascript error in the console

Error: No infusion data found: mw-bi-target
    jQuery 3
    onLoad http://enwiki.localhost:8080/w/load.php?lang=en&modules=ext.checkUser&skin=vector-2022&version=j1rwo:53
    js http://enwiki.localhost:8080/w/load.php?lang=en&modules=ext.checkUser&skin=vector-2022&version=j1rwo:1
    js http://enwiki.localhost:8080/w/load.php?lang=en&modules=ext.checkUser&skin=vector-2022&version=j1rwo:2
    runScript http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:11
    execute http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:13
    doPropagation http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:5
    requestIdleCallback handler*setAndPropagate http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:6
    markModuleReady http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:11
    runScript http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:11
    cssHandle http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:12
    flushCssBuffer http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:4
    addEmbeddedCSS http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:4
    execute http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:12
    doPropagation http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:5
    requestIdleCallback handler*setAndPropagate http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:6
    markModuleReady http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:11
    runScript http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:11
    cssHandle http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:12
    flushCssBuffer http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:4
    addEmbeddedCSS http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:4
    execute http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:12
    doPropagation http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:5
    requestIdleCallback handler*setAndPropagate http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:6
    markModuleReady http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:11
    runScript http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:12
    cssHandle http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:12
    flushCssBuffer http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:4
    addEmbeddedCSS http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:4
    execute http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:12
    doPropagation http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:5
    requestIdleCallback handler*setAndPropagate http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:6
    impl http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:19
    <anonymous> jQuery
    indirectEval http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:10
    asyncEvalTask http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    asyncEval http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    requestIdleCallback handler*asyncEval http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    asyncEvalTask http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    asyncEval http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    requestIdleCallback handler*asyncEval http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    asyncEvalTask http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    asyncEval http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    requestIdleCallback handler*asyncEval http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    asyncEvalTask http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    asyncEval http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    requestIdleCallback handler*asyncEval http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    asyncEvalTask http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    asyncEval http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    requestIdleCallback handler*asyncEval http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    asyncEvalTask http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    asyncEval http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    requestIdleCallback handler*asyncEval http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:15
    work http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:17
    enqueue http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:10
    load http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:19
    <anonymous> http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:46
    <anonymous> http://enwiki.localhost:8080/w/load.php?lang=en&modules=startup&only=scripts&raw=1&skin=vector-2022:46
load.php:3:166
Steps to reproduce problem
  1. On a wiki with CheckUser installed, temporary accounts feature either enabled or known, and $wgUseCodexSpecialBlock = true;
  2. Login as a user with checkuser rights
  3. Open devtools and go to Special:Block
Environment

Browser: Firefox 128. Chromium 131.
Wiki(s): local docker MediaWiki 1.44.0-alpha (c1f7643) 07:01, 14 January 2025. CheckUser 2.5 (0d6ee99) 07:15, 14 January 2025.

Event Timeline

Restricted Application added a subscriber: Aklapper. · View Herald Transcript

Hey @dom_walden, I've given it a good go (both on the latest masters, and the commits mentioned above) but I just cannot replicate this — is it still happening for you?

@TheresNoTime I can replicate this with master branch of CheckUser and MediaWiki core:

image.png (546×2 px, 184 KB)

The steps to reproduce didn't previously indicate that you need to have the temporary accounts feature either enabled or known, so I've added this.

Thanks @Dreamy_Jazz

Unlicking this for a moment as I still can't replicate this even with $wgAutoCreateTempUser['enabled'] = true; set, but now starting to think my checkuser install is scuffed per P72115

nb. I see Exception in module-execute in module ext.checkUser in your stack trace, suggesting this might be a checkuser issue?

nb. I see Exception in module-execute in module ext.checkUser in your stack trace, suggesting this might be a checkuser issue?

It is code from CheckUser, but I am not sure if this is specifically an issue from CheckUser.

The code is supposed to produce the following Show IP button underneath the target field:

image.png (214×982 px, 15 KB)

I can see a mw-bi-target element in the Codex form, but maybe there is no OOUI infusion data for the field (that might make sense given that it's using codex). The relevant code is https://gerrit.wikimedia.org/g/mediawiki/extensions/CheckUser/+/47f36d7557c650aa1d0e88195fbca5c804f36183/modules/ext.checkUser/temporaryaccount/SpecialBlock.js

So what I'm seeing is that the ext.checkUser module needs to be updated to not use OOUI infusion when the block form is built using Codex. The design of where the button goes also probably needs to be looked over given that the target field on the Codex Block form has a link directly underneath it to the contributions page.

So what I'm seeing is that the ext.checkUser module needs to be updated to not use OOUI infusion when the block form is built using Codex. The design of where the button goes also probably needs to be looked over given that the target field on the Codex Block form has a link directly underneath it to the contributions page.

That sounds like it makes sense :-)

So what I'm seeing is that the ext.checkUser module needs to be updated to not use OOUI infusion when the block form is built using Codex. The design of where the button goes also probably needs to be looked over given that the target field on the Codex Block form has a link directly underneath it to the contributions page.

That sounds like it makes sense :-)

Hey again @Dreamy_Jazz / Trust and Safety Product Team — in our team planning meeting just now we discussed this bug as a potential blocker to deployment of Codex Special:Block, and were wondering if this was something y'all would want to work on?

OOUI was removed from Special:Block when $wgUseCodexSpecialBlock is set in r1111378. So I think that takes care of the infusion error.

We met with TST the other day and were informed about the "Show IP" button. I don't really know how to provide this sort integration in Vue… Vue as I understand will re-draw the whole UI, and external DOM changes will be overridden.

I'm thinking a mw.hook system might work? Perhaps combined with a PHP hook for the link.

Let's call the PHP hook SpecialBlockModifyUserLinks. All it takes is the text for a link, and maybe an ID or CSS class name. We send this data to the Vue app and it will dynamically add the link to the "list" of links we already have (which is currently only "Show contributions").

Then on the JS side we have a mw.hook, let's call it specialblock.userlinks. We can fire this in onMounted(), passing the handler the Pinia store instance. ext.CheckUser will add a click listener to the link, and once clicked, make the API request and change the targetUser in the store.

Cparle renamed this task from Error: No infusion data found: mw-bi-target to CheckUser IP reveal does not work on Codex Special:Block.Jan 16 2025, 6:23 PM
Cparle updated the task description. (Show Details)
Cparle updated the task description. (Show Details)

Just to note the current "Show IP" button is replaced with the IP addresses used by the temporary account in the target field, such as in the following example:

image.png (462×1 px, 60 KB)

While my example shows one IP (because it's a local dev environment only one IP is used), this could be showing several IP addresses. Most use one IP address, but there are several temporary accounts on our minor pilot wikis which use more than one IP address. A few temporary accounts on nowiki have used more than 10 different IP addresses. All of these IPs would be showed if the user clicked "Show IP".

This means that using an external link isn't really something that could work (i.e. using the same design as the contributions link), because the link will be replaced with this text and the button is styled specifically to be consistent with the other "Show IP" buttons elsewhere in the MediaWiki interface.

kostajh renamed this task from CheckUser IP reveal does not work on Codex Special:Block to CheckUser temporary account IP reveal does not work on Codex Special:Block.Jan 17 2025, 12:15 PM

This means that using an external link isn't really something that could work (i.e. using the same design as the contributions link), because the link will be replaced with this text and the button is styled specifically to be consistent with the other "Show IP" buttons elsewhere in the MediaWiki interface.

I didn't realize there was this level of interactivity. I have no idea how an external script could inject this kind behaviour into an existing Vue app, if it's even possible at all.

CheckUser is so tightly coupled with the blocking and recentchanges in functionality in core that it doesn't make much conceptual sense for it to be an extension, IMO. That mini-rant aside, I see two options.

Option 1: Embrace the spirit mini-rant above and add a CheckUser-specific pair of hooks (one in PHP and one in JS as you said) in the SpecialBlock code that is specialized to the concept of adding IP addresses. The behavior of replacing the button with the IP addresses on click could be implemented in SpecialBlock instead of in CheckUser, which is a bit awkward but that way the hook only needs to transit information from CheckUser to SpecialBlock (the fact that the link is there, its label, and the IP addresses to display if clicked), not behavior.

Option 2: If you want a general solution that any extension could plug into to add their own interactive element in this spot, then I'd suggest adding a generic hook pair, where the PHP hook still just returns the presence of the link and its label, but the JS hook returns a Vue component. The implementation of the "button that replaces itself with a list of IPs" would live in a Vue component in CheckUser, and CheckUser would return that component object (meaning, the return value of require( './ShowIPButton.vue' )) to the JS hook. The SpecialBlock Vue component would then call that hook and obtain an array of custom Vue components to show from all the extensions that want to show one (in practice it's just CheckUser but we're trying to be generic in this scenario), and it would display them with something like:

<component v-for="customComponent in customComponents" :is="customComponent"></component>

<!-- Alternatively, if you want to allow only one custom component, use this, and set customComponent=null if there isn't one -->
<component v-if="customComponent" :is="customComponent"></component>

If you need to pass information to the custom component, you could pass in props through this <component> tag, or you could pass your Pinia store to the hook so the custom component can access it.

For more information on Vue's <component :is="..."> feature, see the Vue docs on dynamic components and the <component> tag.

Vue as I understand will re-draw the whole UI, and external DOM changes will be overridden.

I don't know exactly what will happen, but what I do know is that it'll be nothing good and that the external DOM additions/changes will probably be blown away at some point, but not immediately (which is probably worse from a user perspective). My guess is that they might last for some time, because as long as Vue is only making minor updates to the DOM (say just the text contents of a tag) it probably won't notice the interlopers, but then when an event happens that requires Vue to make a structural change to the DOM in the vicinity of the additions, I think it'll probably wipe them out. I haven't experimented enough to be able to predict the behavior accurately, but I'm pretty confident the answer looks something like "the DOM changes will remain in place briefly but will eventually be destroyed at an arbitrary-seeming, semi-unpredictable time", and nobody wants that.

I'm thinking a mw.hook system might work? Perhaps combined with a PHP hook for the link.

Let's call the PHP hook SpecialBlockModifyUserLinks. All it takes is the text for a link, and maybe an ID or CSS class name. We send this data to the Vue app and it will dynamically add the link to the "list" of links we already have (which is currently only "Show contributions").

Then on the JS side we have a mw.hook, let's call it specialblock.userlinks. We can fire this in onMounted(), passing the handler the Pinia store instance. ext.CheckUser will add a click listener to the link, and once clicked, make the API request and change the targetUser in the store.

On reflection, I think you might not need the PHP hook as long as you make the JS hook resilient to the possibility that the CheckUser code might load later. You could fire the JS hook early, and pass it an array wrapped in a Vue ref that you expect the caller to modify. You then write your component code to use the ref-wrapped array, and Vue will automatically react to that ref changing. This would look something like:

// In the setup function of the SpecialBlock component:
const customComponents = ref( [] );
mw.hook( 'specialblock.userlinks' ).fire( customComponents );
// Then use customComponents in the <template>
// If it's initially empty and stuff gets added to it later, that's OK, Vue will notice and rerender

// In CheckUser:
const AddIPButton = require( './AddIPButton.vue' );
mw.hook( 'specialblock.userlinks' ).add( function( customComponents ) {
    customComponents.value.push( AddIPButton );
} );

Side note: a cool feature of mw.hook that this exploits is that it allows listeners to be added after the hook has already fired: mw.hook( 'foo' ).add( listener ); mw.hook( 'foo' ).fire(); has the same result as mw.hook( 'foo' ).fire(); mw.hook( 'foo' ).add( listener );. The listener is called either when the hook is fired (if .add() came before .fire()), or immediately when it's added (if .fire() came before .add()).

(Of course if you want the "show IP" link to work without JavaScript, you might still need a PHP hook.)

Thank you, Catrope! This gives me all the answers I need and then some :)

Change #1112316 had a related patch set uploaded (by MusikAnimal; author: MusikAnimal):

[mediawiki/core@master] SpecialBlock [Codex]: add codex.userlookup hook for custom components

https://gerrit.wikimedia.org/r/1112316

Change #1112321 had a related patch set uploaded (by MusikAnimal; author: MusikAnimal):

[mediawiki/extensions/CheckUser@master] SpecialBlock: add UseCodexSpecialBlock integration for 'Show IP' button

https://gerrit.wikimedia.org/r/1112321

Change #1112316 merged by jenkins-bot:

[mediawiki/core@master] SpecialBlock [Codex]: add codex.userlookup hook for custom components

https://gerrit.wikimedia.org/r/1112316

Change #1112853 had a related patch set uploaded (by MusikAnimal; author: MusikAnimal):

[mediawiki/core@master] UserLookup.vue: Only pass custom components a valid target

https://gerrit.wikimedia.org/r/1112853

Change #1112853 merged by jenkins-bot:

[mediawiki/core@master] UserLookup.vue: Only pass custom components a valid target

https://gerrit.wikimedia.org/r/1112853

Change #1113448 had a related patch set uploaded (by Dreamy Jazz; author: Dreamy Jazz):

[mediawiki/extensions/CheckUser@master] Add QUnit tests for UseCodexSpecialBlock integration

https://gerrit.wikimedia.org/r/1113448

Change #1112321 merged by jenkins-bot:

[mediawiki/extensions/CheckUser@master] SpecialBlock: add UseCodexSpecialBlock integration for 'Show IP' button

https://gerrit.wikimedia.org/r/1112321

Change #1113448 merged by jenkins-bot:

[mediawiki/extensions/CheckUser@master] Add QUnit tests for UseCodexSpecialBlock integration

https://gerrit.wikimedia.org/r/1113448

I see the Show IP link now loading for temporary account targets. It makes the correct API request and displays all the IPs.

Test environment: local docker MediaWiki 1.44.0-alpha (4a859d0) 09:52, 18 February 2025.

Change #1123476 had a related patch set uploaded (by MusikAnimal; author: MusikAnimal):

[mediawiki/core@master] UserLookup.vue: give the target input the ID mw-bi-target

https://gerrit.wikimedia.org/r/1123476

Change #1123476 merged by jenkins-bot:

[mediawiki/core@master] UserLookup.vue: give the target input the ID mw-bi-target

https://gerrit.wikimedia.org/r/1123476