openvibers/openvibe.blog Commits

SEO-7: the editor answers a signed-out browser here, noindex, instead of bouncing it to Network sign-in (#114)

OP OpenVibers committed 15 days ago · 682d692a33 · 2 files, +20 −4

/write was a 303 to /auth/login, which 302s to openvibe.network/oauth/authorize, whose page
carries <link rel="canonical" href="https://openvibe.network/login">. tools/seo-audit.js read
that canonical as this page's, so openvibe.blog/write was reported as a cross-origin canonical —
and a crawler saw no page of ours at all.

Every /write GET for a signed-out visitor now renders the site's own message page: 401,
X-Robots-Tag noindex, nofollow, no cross-origin canonical, and a link straight to
/auth/login?next=… so a person is where the redirect used to take them. POSTs still redirect:
their session ended and sign-in returns them to the editor.

test/nojs-editor.test.js asserts the rule for the gated route (noindex, no canonical off this
origin) and for a public page (self-canonical, indexable).

Changes

ddiff --git a/server/http/editor.js b/server/http/editor.js
iindex c9f7d61..95c4f83 100644
---- a/server/http/editor.js
++++ b/server/http/editor.js
@@@ -44,7 +44,10 @@ function createEditorRoutes(ctx) {
res.set('X-Robots-Tag', 'noindex, nofollow');
res.vary('Cookie');
if (req.viewer.kind !== 'user') {
-- if (req.method === 'GET') return res.redirect(303, `/auth/login?next=${encodeURIComponent(req.originalUrl)}`);
++ // A gated page answers on this origin, noindex, instead of bouncing to sign-in: a crawler
++ // that followed the redirect read openvibe.network's canonical as this page's. The page
++ // links to sign-in, so a person is one click from where the redirect used to take them.
++ if (req.method === 'GET') return publicRoutes.messagePage(req, res, 401, 'Sign in to write', 'The editor is for OpenVibe accounts. Sign in and you come straight back here.', { href: `/auth/login?next=${encodeURIComponent(req.originalUrl)}`, label: 'Sign in with OpenVibe' });
return publicRoutes.messagePage(req, res, 401, 'Sign in first', 'Your session ended. Sign in and try again.', { href: `/auth/login?next=${encodeURIComponent('/write')}`, label: 'Sign in' });
}
if (!req.viewer.subject) return publicRoutes.messagePage(req, res, 403, 'Account not ready', 'Your OpenVibe sign-in did not include an account id. Sign out and in again.');
ddiff --git a/test/nojs-editor.test.js b/test/nojs-editor.test.js
iindex fef9693..d7e3043 100644
---- a/test/nojs-editor.test.js
++++ b/test/nojs-editor.test.js
@@@ -16,15 +16,28 @@ const idFrom = (location) => (location.match(/\/write\/posts\/(pst_[0-9A-Z]+)/)
const csrf = t.csrf(eve);
let postId;
-- await check('the editor sends a signed-out browser to Network sign-in', async () => {
++ await check('the editor answers a signed-out browser here, noindex, and links to Network sign-in', async () => {
const r = await t.get('/write');
-- assert.strictEqual(r.status, 303);
-- assert.strictEqual(r.headers.get('location'), '/auth/login?next=%2Fwrite');
++ assert.strictEqual(r.status, 401);
++ assert.match(r.headers.get('x-robots-tag'), /noindex/);
++ assert.match(r.text, /<meta name="robots" content="noindex, nofollow">/);
++ assert.match(r.text, /href="\/auth\/login\?next=%2Fwrite"/, 'one click from signing in');
++ // The rule (SEO-7): a gated page never names another origin as its canonical, so a crawler that
++ // reads this page is not told the Network login page is the page it asked for.
++ const canon = (/<link rel="canonical" href="([^"]+)"/.exec(r.text) || [])[1];
++ assert.ok(!canon || new URL(canon).origin === 'https://openvibe.blog', `canonical ${canon}`);
const login = await t.get('/auth/login?next=/write');
assert.strictEqual(login.status, 302);
assert.match(login.headers.get('location'), /\/oauth\/authorize\?.*client_id=blog/);
});
++ await check('a public page still keeps its own canonical', async () => {
++ const r = await t.get('/');
++ assert.strictEqual(r.status, 200);
++ assert.match(r.text, /<link rel="canonical" href="https:\/\/openvibe\.blog\/"/);
++ assert.doesNotMatch(r.text, /<meta name="robots" content="noindex/);
++ });
++
await check('first use: the start form creates the member blog', async () => {
const home = await t.get('/write', { as: eve });
assert.strictEqual(home.status, 200);