diff --git a/projects/packages/jetpack-mu-wpcom/changelog/cm-841-surface-login-requirement b/projects/packages/jetpack-mu-wpcom/changelog/cm-841-surface-login-requirement new file mode 100644 index 000000000000..02fec7816e12 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/cm-841-surface-login-requirement @@ -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. diff --git a/projects/packages/jetpack-mu-wpcom/changelog/verbum-iframe-social-login-storage-access b/projects/packages/jetpack-mu-wpcom/changelog/verbum-iframe-social-login-storage-access new file mode 100644 index 000000000000..11fbad5e0f61 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/verbum-iframe-social-login-storage-access @@ -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. diff --git a/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/class-verbum-comments.php b/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/class-verbum-comments.php index 59a7025e7b0a..5451005a6c6c 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/class-verbum-comments.php +++ b/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/class-verbum-comments.php @@ -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' ), diff --git a/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/src/components/logged-out.tsx b/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/src/components/logged-out.tsx index 1b96b5824aad..8870ee482076 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/src/components/logged-out.tsx +++ b/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/src/components/logged-out.tsx @@ -40,6 +40,31 @@ const getLoginCommentText = ( commentParent: Signal ) => { return { defaultText }; }; +/** + * 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' : ''; @@ -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 ( +
+
+
+
+ { getLoginCommentText( commentParent ) } +
+ + { translate( 'Log in' ) } + +
+
+
+ ); + } + return (
- { canWeAccessCookies && ( + { showSocialButtons && ( <>
{ getLoginCommentText( commentParent ) } @@ -147,7 +202,21 @@ export const LoggedOut = ( { login, canWeAccessCookies, loginWindow }: LoggedOut
) } - + { showSiteLogin && showSocialButtons && ( + + { translate( 'Log in' ) } + + ) } +
diff --git a/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/src/hooks/useSocialLogin.tsx b/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/src/hooks/useSocialLogin.tsx index d5bc01afe0dc..dae6a5476de5 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/src/hooks/useSocialLogin.tsx +++ b/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/src/hooks/useSocialLogin.tsx @@ -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. * @@ -72,9 +91,18 @@ 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 }`, @@ -82,6 +110,11 @@ export default function useSocialLogin() { `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 && @@ -112,7 +145,7 @@ 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( () => { @@ -120,8 +153,8 @@ export default function useSocialLogin() { clearInterval( loginClosed ); setLoginWindowRef( undefined ); window.removeEventListener( 'message', waitForLogin ); - broadcastChannel.removeEventListener( 'message', waitForLogin ); - broadcastChannel.close(); + broadcastChannel?.removeEventListener( 'message', waitForLogin ); + broadcastChannel?.close(); } }, 100 ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/src/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/src/style.scss index 7f88cd7861db..841fc41dd7d6 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/src/style.scss +++ b/projects/packages/jetpack-mu-wpcom/src/features/verbum-comments/src/style.scss @@ -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; diff --git a/projects/plugins/jetpack/changelog/verbum-iframe-social-login-storage-access b/projects/plugins/jetpack/changelog/verbum-iframe-social-login-storage-access new file mode 100644 index 000000000000..1a3685ab23ee --- /dev/null +++ b/projects/plugins/jetpack/changelog/verbum-iframe-social-login-storage-access @@ -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. diff --git a/projects/plugins/jetpack/modules/comments/comments.php b/projects/plugins/jetpack/modules/comments/comments.php index 5082713660a9..e323419bbfc5 100644 --- a/projects/plugins/jetpack/modules/comments/comments.php +++ b/projects/plugins/jetpack/modules/comments/comments.php @@ -518,7 +518,8 @@ public function comment_form_after() { class="jetpack_remote_comment" id="jetpack_remote_comment" - sandbox="allow-same-origin allow-top-navigation allow-scripts allow-forms allow-popups" + + sandbox="allow-same-origin allow-top-navigation allow-scripts allow-forms allow-popups allow-storage-access-by-user-activation" >