openvibers/openvibe.blog Commits
SEO-7: the editor answers a signed-out browser here, noindex, instead of bouncing it to Network sign-in (#114)
Changes
@@@ -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.');@@@ -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);
@@ -44,7 +44,10 @@ function createEditorRoutes(ctx) {
@@ -44,7 +44,10 @@ function createEditorRoutes(ctx) {
res.set('X-Robots-Tag', 'noindex, nofollow');
res.set('X-Robots-Tag', 'noindex, nofollow');
res.vary('Cookie');
res.vary('Cookie');
if (req.viewer.kind !== 'user') {
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' });
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.');
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.');
@@ -16,15 +16,28 @@ const idFrom = (location) => (location.match(/\/write\/posts\/(pst_[0-9A-Z]+)/)
@@ -16,15 +16,28 @@ const idFrom = (location) => (location.match(/\/write\/posts\/(pst_[0-9A-Z]+)/)
const csrf = t.csrf(eve);
const csrf = t.csrf(eve);
let postId;
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');
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');
const login = await t.get('/auth/login?next=/write');
assert.strictEqual(login.status, 302);
assert.strictEqual(login.status, 302);
assert.match(login.headers.get('location'), /\/oauth\/authorize\?.*client_id=blog/);
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 () => {
await check('first use: the start form creates the member blog', async () => {
const home = await t.get('/write', { as: eve });
const home = await t.get('/write', { as: eve });
assert.strictEqual(home.status, 200);
assert.strictEqual(home.status, 200);