Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Verbum Comments: surface the login requirement up front when registration is required but in-frame social login is unavailable, instead of showing a guest form that is rejected on submit.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Verbum Comments: allow social login (WordPress.com / Facebook) inside the cross-origin comment iframe on Atomic/Jetpack sites by requesting first-party cookie access via the Storage Access API.
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ public function enqueue_assets() {
/* translators: %s is the name of the provider (WordPress, Facebook, Twitter) */
'Logged in via %s' => __( 'Logged in via %s', 'jetpack-mu-wpcom' ),
'Log out' => __( 'Log out', 'jetpack-mu-wpcom' ),
'Log in' => __( 'Log in', 'jetpack-mu-wpcom' ),
'Email' => __( 'Email', 'jetpack-mu-wpcom' ),
'(Address never made public)' => __( '(Address never made public)', 'jetpack-mu-wpcom'), // phpcs:ignore PEAR.Functions.FunctionCallSignature.SpaceBeforeCloseBracket
'Instantly' => __( 'Instantly', 'jetpack-mu-wpcom' ),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,31 @@ const getLoginCommentText = ( commentParent: Signal ) => {
return <span>{ defaultText }</span>;
};

/**
* Build a top-level login URL for the site the comment form belongs to.
*
* On Atomic/Jetpack the comment form runs inside a cross-origin iframe, so the parent post URL
* is passed in via the location hash (`#parent=…`). We log in against that site so the visitor
* returns authenticated (via Jetpack SSO) and can comment.
*
* @return {string} - The site's login URL, or the bare target if it can't be parsed.
*/
const getSiteLoginUrl = () => {
const parentUrl = decodeURIComponent(
window.location.hash.match( /[#&]parent=([^&]*)/ )?.[ 1 ] ?? ''
);

const target = parentUrl || VerbumComments.homeURL || '';

try {
return `${ new URL( target ).origin }/wp-login.php?redirect_to=${ encodeURIComponent(
target
) }`;
} catch {
return target;
}
};

export const LoggedOut = ( { login, canWeAccessCookies, loginWindow }: LoggedOutProps ) => {
const [ activeService, setActiveService ] = useState( '' );
const closeLoginPopupService = requireNameEmail && ! mustLogIn ? 'mail' : '';
Expand Down Expand Up @@ -84,11 +109,41 @@ export const LoggedOut = ( { login, canWeAccessCookies, loginWindow }: LoggedOut

const { commentParent } = useContext( VerbumSignals );

// In the iframe (Atomic/Jetpack) we offer social login and request cookie access on click,
// so the buttons render even when the cookie test currently fails.
const showSocialButtons = canWeAccessCookies || !! VerbumComments.isJetpackComments;

// Login is required but cookies are blocked, so an in-frame login may not stick. Keep the
// top-level login link on offer — without it, a denied cookie prompt is a dead end again.
const showSiteLogin = mustLogIn && ! canWeAccessCookies;

if ( showSiteLogin && ! showSocialButtons ) {
return (
<div className="verbum-subscriptions logged-out">
<div className="verbum-subscriptions__wrapper">
<div className="verbum-subscriptions__login verbum-subscriptions__login-required">
<div className="verbum-subscriptions__login-header">
{ getLoginCommentText( commentParent ) }
</div>
<a
className="components-button is-primary"
href={ getSiteLoginUrl() }
target="_top"
rel="noopener noreferrer"
>
{ translate( 'Log in' ) }
</a>
</div>
</div>
</div>
);
}

return (
<div className="verbum-subscriptions logged-out">
<div className="verbum-subscriptions__wrapper">
<div className="verbum-subscriptions__login">
{ canWeAccessCookies && (
{ showSocialButtons && (
<>
<div className="verbum-subscriptions__login-header">
{ getLoginCommentText( commentParent ) }
Expand Down Expand Up @@ -147,7 +202,21 @@ export const LoggedOut = ( { login, canWeAccessCookies, loginWindow }: LoggedOut
</div>
</>
) }
<EmailForm shouldShowEmailForm={ activeService === 'mail' || ! canWeAccessCookies } />
{ showSiteLogin && showSocialButtons && (
<a
className="components-button is-link verbum-subscriptions__login-link"
href={ getSiteLoginUrl() }
target="_top"
rel="noopener noreferrer"
>
{ translate( 'Log in' ) }
</a>
) }
<EmailForm
shouldShowEmailForm={
activeService === 'mail' || ( ! canWeAccessCookies && ! mustLogIn )
}
/>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,25 @@ const addWordPressDomain = window.location.hostname.endsWith( '.wordpress.com' )
? ' Domain=.wordpress.com'
: '';

/**
* Ask the browser to grant this (cross-origin) frame access to its first-party cookies.
*
* On Atomic/Jetpack, Verbum runs inside a third-party iframe where the `.wordpress.com` auth
* cookies can't be read or written, so a social login never sticks — and the comment POST back to
* jetpack.wordpress.com arrives without a session, which WP.com downgrades to a guest comment.
*
* Best-effort: not awaited, so the login popup still opens on the same click. `window.open()` and
* this both want the user gesture, and the popup is the one that breaks visibly without it. If
* access is denied the visitor stays logged out and the top-level login link remains available.
*/
const requestCookieAccess = () => {
try {
document.requestStorageAccess?.().catch( () => {} );
} catch {
// Unsupported — the caller handles the still-logged-out case.
}
};

/**
* Hook to retrieve user info from server, handle social login, and logout functionality.
*
Expand Down Expand Up @@ -72,16 +91,30 @@ export default function useSocialLogin() {
document.cookie = `${ cookieName }=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=None; Secure=True;${ addWordPressDomain }`;
};

const login = async ( service: SocialServiceName ) => {
const login = ( service: SocialServiceName ) => {
const { connectURL } = VerbumComments;
const broadcastChannel = new BroadcastChannel( 'verbum_post_message' );

// BroadcastChannel needs storage access, which a cookie-blocked iframe doesn't have — Firefox
// throws SecurityError. It's a secondary channel: the popup also posts back via
// window.postMessage, picked up by the listener below, so carry on without it.
let broadcastChannel: BroadcastChannel | null = null;
try {
broadcastChannel = new BroadcastChannel( 'verbum_post_message' );
} catch {
// No channel available.
}

const loginWindow = window.open(
`${ connectURL }&blog_id=${ VerbumComments.siteId }&post_id=${ VerbumComments.postId }&service=${ service }`,
'VerbumCommentsLogin',
`status=0,toolbar=0,location=1,menubar=0,directories=0,resizable=1,scrollbars=0${ serviceData[ service ].popup }`
);

// Requested once the popup is open, so it resolves while the visitor is still logging in.
if ( VerbumComments.isJetpackComments ) {
requestCookieAccess();
}

const waitForLogin = ( event: MessageEvent ) => {
if (
event.origin !== document.location.origin &&
Expand Down Expand Up @@ -112,16 +145,16 @@ export default function useSocialLogin() {

// Listen for login data
window.addEventListener( 'message', waitForLogin );
broadcastChannel.addEventListener( 'message', waitForLogin );
broadcastChannel?.addEventListener( 'message', waitForLogin );

// Clean up loginWindow to reset activeService
const loginClosed = setInterval( () => {
if ( loginWindow?.closed ) {
clearInterval( loginClosed );
setLoginWindowRef( undefined );
window.removeEventListener( 'message', waitForLogin );
broadcastChannel.removeEventListener( 'message', waitForLogin );
broadcastChannel.close();
broadcastChannel?.removeEventListener( 'message', waitForLogin );
broadcastChannel?.close();
}
}, 100 );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,18 @@
padding-bottom: 16px;
}

.verbum-subscriptions__login-required {

.components-button.is-primary {
text-decoration: none;
}
}

.verbum-subscriptions__login-link {
display: inline-block;
margin-block-start: 8px;
}

.verbum-logins {
display: flex;
align-items: center;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: enhancement

Comments: allow the comment form iframe to request first-party cookie access, so social login can work inside it.
3 changes: 2 additions & 1 deletion projects/plugins/jetpack/modules/comments/comments.php
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,8 @@ public function comment_form_after() {
<?php endif; ?>
class="jetpack_remote_comment"
id="jetpack_remote_comment"
sandbox="allow-same-origin allow-top-navigation allow-scripts allow-forms allow-popups"
<?php // `allow-storage-access-by-user-activation` lets the form request first-party cookie access; without it the browser refuses the request outright. ?>
sandbox="allow-same-origin allow-top-navigation allow-scripts allow-forms allow-popups allow-storage-access-by-user-activation"
>
<?php if ( $is_amp ) : ?>
<button overflow><?php esc_html_e( 'Show more', 'jetpack' ); ?></button>
Expand Down
Loading