Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions client/blocks/login/utils/get-blackbox-session-id.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,29 @@
* third-party script misbehaves — Blackbox must never block login.
* @returns {Promise<string|undefined>} Session ID, or undefined on failure.
*/
async function runBlackboxCollect() {
if ( ! window.Blackbox?.collect ) {
return;
}

try {
const out = window.Blackbox.collect();
if ( out && typeof out.then === 'function' ) {
await Promise.race( [ out, new Promise( ( resolve ) => setTimeout( resolve, 2000 ) ) ] );
}
} catch {
// Intentionally ignored — Blackbox must never block login.
}
}

export async function getBlackboxSessionId() {
if ( ! window.Blackbox?.getSessionId ) {
return undefined;
}

try {
await runBlackboxCollect();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can just change window.Blackbox.getSessionId() below to window.Blackbox.collect(), and then return result.sessionId.

I suppose we're not actually checking the result for a challenge here and blocking the login submission yet?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we're not actually checking the result for a challenge here and blocking the login submission yet?

Would this not happen server side as part of enforcement otherwise you could avoid the challenge by doing your own post?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual verification of the session happens server-side, but if collect returns a challenge shouldn't we wait to let them solve the challenge?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah so block the submit button until challenge is solved.


const result = await Promise.race( [
window.Blackbox.getSessionId(),
new Promise( ( resolve ) => setTimeout( resolve, 2000 ) ),
Expand Down
62 changes: 62 additions & 0 deletions client/blocks/login/utils/test/get-blackbox-session-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* @jest-environment jsdom
*/
import { getBlackboxSessionId } from '../get-blackbox-session-id';

describe( 'getBlackboxSessionId', () => {
afterEach( () => {
delete window.Blackbox;
} );

test( 'calls collect before getSessionId when both exist', async () => {
const order = [];
window.Blackbox = {
collect: jest.fn( () => {
order.push( 'collect' );
} ),
getSessionId: jest.fn( () => {
order.push( 'getSessionId' );
return Promise.resolve( 'session-id' );
} ),
};

await expect( getBlackboxSessionId() ).resolves.toBe( 'session-id' );
expect( order ).toEqual( [ 'collect', 'getSessionId' ] );
} );

test( 'works when collect is missing', async () => {
window.Blackbox = {
getSessionId: jest.fn( () => Promise.resolve( 'session-id' ) ),
};

await expect( getBlackboxSessionId() ).resolves.toBe( 'session-id' );
expect( window.Blackbox.getSessionId ).toHaveBeenCalled();
} );

test( 'awaits async collect before getSessionId', async () => {
const order = [];
window.Blackbox = {
collect: jest.fn( () =>
Promise.resolve().then( () => {
order.push( 'collect' );
} )
),
getSessionId: jest.fn( () => {
order.push( 'getSessionId' );
return Promise.resolve( 'session-id' );
} ),
};

await expect( getBlackboxSessionId() ).resolves.toBe( 'session-id' );
expect( order ).toEqual( [ 'collect', 'getSessionId' ] );
} );

test( 'returns undefined when getSessionId is missing', async () => {
window.Blackbox = {
collect: jest.fn(),
};

await expect( getBlackboxSessionId() ).resolves.toBeUndefined();
expect( window.Blackbox.collect ).not.toHaveBeenCalled();
} );
} );
27 changes: 17 additions & 10 deletions client/document/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,13 @@ class Document extends Component {
}
}

const isBlackboxLoginEnabled =
sectionName === 'login' &&
config.isEnabled( 'blackbox-login' ) &&
config( 'blackbox_api_key' );
const blackboxChallengeRootId = 'blackbox-challenge-root';
const blackboxChallengeRootSelector = `#${ blackboxChallengeRootId }`;

return (
<html lang={ lang } dir={ isRTL ? 'rtl' : 'ltr' }>
<Head
Expand Down Expand Up @@ -173,6 +180,7 @@ class Document extends Component {
/>
</div>
) }
{ isBlackboxLoginEnabled && <div id={ blackboxChallengeRootId } /> }
{ renderedLayout ? (
<div
id="wpcom"
Expand Down Expand Up @@ -259,16 +267,15 @@ class Document extends Component {
/>
) }

{ sectionName === 'login' &&
config.isEnabled( 'blackbox-login' ) &&
config( 'blackbox_api_key' ) && (
<script
nonce={ inlineScriptNonce }
defer
src={ config( 'blackbox_url' ) }
data-apikey={ config( 'blackbox_api_key' ) }
/>
) }
{ isBlackboxLoginEnabled && (
<script
nonce={ inlineScriptNonce }
defer
src={ config( 'blackbox_url' ) }
data-apikey={ config( 'blackbox_api_key' ) }
data-challenge-container={ blackboxChallengeRootSelector }
/>
) }

{ entrypoint?.language?.manifest && (
<script nonce={ inlineScriptNonce } src={ entrypoint.language.manifest } />
Expand Down
6 changes: 6 additions & 0 deletions client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,11 @@ declare global {
};
currentUser?: User;
__REDUX_DEVTOOLS_EXTENSION__?: () => void;
/** Blackbox-js bot detection (login); loaded from blackbox-api.wp.com. */
Blackbox?: {
collect?: () => unknown;
getSessionId?: () => Promise< unknown >;
reset?: () => void;
};
}
}
Loading