openvibers/openvibe.locker Commits

Locker home polish; sitemap and robots; Cloudflare beacon in the CSP (#3)

OP OpenVibers committed 5 days ago · b5d790ed6e · 4 files, +16 −4

Changes

ddiff --git a/public/css/locker.css b/public/css/locker.css
iindex e0027f9..3b5e014 100644
---- a/public/css/locker.css
++++ b/public/css/locker.css
@@@ -8,3 +8,5 @@ html body{background:var(--lk-bg);color:var(--lk-text)}.locker-page,.locker-page
html[data-theme-mode=dark] .hero em{color:#b9df9d}html[data-theme-mode=light] .hero em{color:#426b36}
html[data-theme-mode=dark] .art-caption{color:#aecb9b}html[data-theme-mode=light] .art-caption{color:#456233}
.locker-page .ov-sel-trigger{min-height:46px}
++
++@media(max-width:700px){.hero .actions{display:grid;grid-template-columns:minmax(0,1fr);gap:10px}.hero .actions .button{justify-content:center;text-align:center}}
ddiff --git a/public/js/app.js b/public/js/app.js
iindex 91ee9d5..792ae69 100644
---- a/public/js/app.js
++++ b/public/js/app.js
@@@ -267,7 +267,7 @@
}
}
} catch (e) {
-- if (e.status === 401 && !['send', 'docs'].includes(page.mode)) {
++ if (e.status === 401 && !['send', 'docs', 'home'].includes(page.mode)) {
const a = make('a', 'Sign in to OpenVibe'); a.href = `/auth/login?next=${encodeURIComponent(location.pathname)}`;
$('#notice').replaceChildren(a);
} else if (e.status !== 401) notice(e.message, true);
ddiff --git a/server/app.js b/server/app.js
iindex 496c482..32a77b5 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -30,9 +30,9 @@ async function createApp(options = {}) {
const app = express();
app.disable('x-powered-by'); app.set('trust proxy', config.trustProxy);
app.use(helmet({ contentSecurityPolicy: { directives: {
-- defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'", config.networkUrl], styleSrc: ["'self'", "'unsafe-inline'", config.networkUrl, 'https://fonts.googleapis.com'],
++ defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'", config.networkUrl, 'https://static.cloudflareinsights.com'], styleSrc: ["'self'", "'unsafe-inline'", config.networkUrl, 'https://fonts.googleapis.com'],
fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com'], imgSrc: ["'self'", 'data:', 'blob:', config.networkUrl],
-- connectSrc: ["'self'", config.networkUrl, 'https://openvibe.chat', 'https://billing.openvibe.network', 'https://openvibe.events'],
++ connectSrc: ["'self'", config.networkUrl, 'https://openvibe.chat', 'https://billing.openvibe.network', 'https://openvibe.events', 'https://cloudflareinsights.com'],
workerSrc: ["'self'"], frameSrc: [config.networkUrl], frameAncestors: ["'none'"], objectSrc: ["'none'"], baseUri: ["'self'"],
formAction: ["'self'", config.networkUrl], upgradeInsecureRequests: config.production ? [] : null,
} }, crossOriginEmbedderPolicy: false, referrerPolicy: { policy: 'no-referrer' } }));
ddiff --git a/server/pages.js b/server/pages.js
iindex dff8e10..5990613 100644
---- a/server/pages.js
++++ b/server/pages.js
@@@ -22,7 +22,7 @@ function createPages({ config }) {
body: `<div id="navbar-mount"></div><main id="main" class="locker-page" data-mode="${mode}"><div id="notice" role="status" aria-live="polite"></div>${body}</main><script>window.LockerPage=${boot};window.OpenVibeLockerConfig={network:'',reauthUrl:'/reauth'};</script><script src="/shared/ov-locker-core.js"></script><script src="/shared/ov-locker.js"></script><script src="/shared/ov-share.js"></script><script src="/js/payload.js"></script><script src="/js/app.js"></script>`,
}));
}
-- r.get('/', (req, res) => page(req, res, 'Keep it yours', `<header class="hero"><div><p class="eyebrow">OPENVIBE LOCKER</p><h1>Keep it<br><em>yours.</em></h1><p class="lead">A private place for your files.<br>Locked on your device, before they leave it.</p><div class="actions"><a class="button primary" href="/drive">Open your Drive <span aria-hidden="true">↗</span></a><a class="button" href="/sends">Send something privately</a></div></div><div class="lock-art" aria-hidden="true"><div class="shackle"></div><div class="lock-body"><span></span></div><span class="art-caption">ONLY YOU HOLD THE KEYS</span></div></header>${device}<section class="grid"><a class="panel feature" href="/drive"><span class="number">01 / DRIVE</span><h2>Your files.<br>Your keys.</h2><p>Files and folders encrypted on your device. Share with people you choose.</p><span class="link-label">Go to Drive ↗</span></a><a class="panel feature" href="/sends"><span class="number">02 / SENDS</span><h2>A link with<br>a time limit.</h2><p>Send a file or a private note. Choose when it expires and how often it can be opened.</p><span class="link-label">Create a Send ↗</span></a></section><section class="plain"><h2>Private, including from us.</h2><p>OpenVibe stores encrypted bytes. Names, file types and content stay inside the lock. We can see the owner, padded size and time.</p><p>${warning}</p><a href="/recovery">Set up your locker and keep a recovery kit →</a></section>`, 'home'));
++ r.get('/', (req, res) => page(req, res, 'Keep it yours', `<header class="hero"><div><p class="eyebrow">OPENVIBE LOCKER</p><h1>Keep it<br><em>yours.</em></h1><p class="lead">A private place for your files.<br>Locked on your device, before they leave it.</p><div class="actions"><a class="button primary" href="/drive">Open your Drive</a><a class="button" href="/sends">Send something privately</a></div></div><div class="lock-art" aria-hidden="true"><div class="shackle"></div><div class="lock-body"><span></span></div><span class="art-caption">ONLY YOU HOLD THE KEYS</span></div></header>${device}<section class="grid"><a class="panel feature" href="/drive"><span class="number">01 / DRIVE</span><h2>Your files.<br>Your keys.</h2><p>Files and folders encrypted on your device. Share with people you choose.</p><span class="link-label">Go to Drive ↗</span></a><a class="panel feature" href="/sends"><span class="number">02 / SENDS</span><h2>A link with<br>a time limit.</h2><p>Send a file or a private note. Choose when it expires and how often it can be opened.</p><span class="link-label">Create a Send ↗</span></a></section><section class="plain"><h2>Private, including from us.</h2><p>OpenVibe stores encrypted bytes. Names, file types and content stay inside the lock. We can see the owner, padded size and time.</p><p>${warning}</p><a href="/recovery">Set up your locker and keep a recovery kit →</a></section>`, 'home'));
r.get('/drive', (req, res) => page(req, res, 'Drive', `<header class="page-heading"><p class="eyebrow">YOUR PRIVATE SPACE</p><h1>Drive</h1><p>Files and folders that only you and the people you choose can open.</p></header>${device}<div id="account" class="storage"></div><section class="panel"><div class="section-heading"><h2>Your files</h2><a href="/files/shared">Shared with you ↗</a></div><div class="upload-grid"><form id="upload-form"><label>Choose a file<input name="file" type="file" required></label><label>Folder<select id="upload-parent" name="parent"><option value="">Drive</option></select></label><button class="primary">Lock and upload</button></form><form id="folder-form"><label>New folder name<input name="name" maxlength="200" required autocomplete="off"></label><button>Create locked folder</button></form></div><p id="progress" role="status"></p><nav id="folders" aria-label="Folders" class="actions"></nav><div id="items" class="item-list"><p class="empty">Sign in to see your encrypted files.</p></div></section>`, 'drive'));
r.get('/sends', (req, res) => page(req, res, 'Sends', `<header class="page-heading"><p class="eyebrow">PRIVATE, FOR A WHILE</p><h1>Sends</h1><p>A locked file or note. The recipient needs only the full link.</p></header><section class="panel"><h2>Create a Send</h2><form id="send-form"><div class="grid"><label>A file<input name="file" type="file"></label><label>Or a private note<textarea name="text" rows="4" maxlength="1048576" placeholder="Write your note here"></textarea></label></div><div class="grid"><label>Expires after<select name="expires"><option value="3600">1 hour</option><option value="86400" selected>1 day</option><option value="604800">7 days</option><option value="2592000">30 days</option></select></label><label>Download limit (optional)<input name="limit" type="number" min="1" max="100000" placeholder="Until it expires"></label></div><p class="warning">${warning}</p><button class="primary">Lock and create link</button></form><p id="progress" role="status"></p><div id="send-result"></div></section><section class="plain"><p>The key stays after # in the link and is never sent to OpenVibe. Anyone with the full link can open the Send and keep a copy. Each download attempt counts, including interrupted downloads.</p></section><section class="panel"><h2>Your Sends</h2><div id="items" class="item-list"><p class="empty">Sign in to create and manage Sends.</p></div></section>`, 'sends'));
r.get('/recovery', (req, res) => page(req, res, 'Recovery kit', `<header class="page-heading"><p class="eyebrow">KEEP A WAY BACK IN</p><h1>Your recovery kit.</h1><p>24 words. Shown once. Kept by you.</p></header>${device}<section class="panel recovery"><h2>Make your locker</h2><p>Choose a passphrase or a supported passkey on this device. Your recovery kit can restore your keys on a new device.</p><p class="warning">${warning}</p><div class="actions">${button('setup', 'Set up my locker', 'primary')}${button('unlock', 'I already have a locker')}</div><p>The setup screen lets you print or save the words, then checks two of them. Store the kit somewhere only you can reach. OpenVibe cannot display it again.</p><p>A passkey belongs to this site and device. If it is unavailable, use your recovery kit.</p></section>`, 'recovery'));
@@@ -30,6 +30,16 @@ function createPages({ config }) {
r.get('/files/shared', (req, res) => page(req, res, 'Shared with you', `<header class="page-heading"><p class="eyebrow">FOR YOUR EYES</p><h1>Shared with you.</h1></header>${device}<section class="panel"><div id="items" class="item-list"></div></section>`, 'shared'));
r.get('/files/:id', (req, res) => { if (!ID.test(req.params.id)) return res.sendStatus(404); page(req, res, 'A locked file', `<header class="page-heading"><p class="eyebrow">OPEN ON YOUR DEVICE</p><h1 id="file-name">A locked file.</h1><p id="item-description">Its name and content are encrypted.</p></header>${device}<section class="panel"><div id="opened"></div><div class="actions">${button('download', 'Open and download', 'primary')}<span data-owner-only hidden>${button('share', 'Share with people')}</span></div><form id="rename-form" data-owner-only hidden><label>New file name<input name="name" required maxlength="200" autocomplete="off"></label><button>Rename inside the lock</button></form><p id="progress" role="status"></p></section>${report}`, 'file', req.params.id); });
r.get('/staff', (req, res) => page(req, res, 'Reports', '<header class="page-heading"><p class="eyebrow">MODERATION</p><h1>Reports</h1><p>Review reports by item and account. Encrypted content stays private.</p></header><section class="panel"><div id="reports"></div></section>', 'staff'));
++ // The public pages only: Drive, Sends, files and the recovery kit are private and stay out (the head links here).
++ r.get('/sitemap.xml', (_req, res) => {
++ const base = String(config.baseUrl || 'https://openvibe.locker').replace(/\/+$/, '');
++ const urls = ['/', '/docs'].map((p) => `<url><loc>${base}${p}</loc></url>`).join('');
++ res.type('application/xml').set('Cache-Control', 'public, max-age=3600').send(`<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${urls}</urlset>`);
++ });
++ r.get('/robots.txt', (_req, res) => {
++ const base = String(config.baseUrl || 'https://openvibe.locker').replace(/\/+$/, '');
++ res.type('text/plain').set('Cache-Control', 'public, max-age=3600').send(`User-agent: *\nAllow: /$\nAllow: /docs\nDisallow: /\nSitemap: ${base}/sitemap.xml\n`);
++ });
r.get('/docs', (req, res) => page(req, res, 'Locked items on OpenVibe', `<header class="page-heading"><p class="eyebrow">FOR PEOPLE AND AGENTS</p><h1>Locked items<br>on OpenVibe.</h1></header><article class="panel prose"><h2>Three ways in</h2><p>A people lock opens with the keys of the people on its list. A password lock opens with its password. A link lock opens with a key after #. The same Locker envelope works across OpenVibe.</p><h2>Drive and Sends</h2><p>Drive uses people locks. Sends use link locks, expire in 1 hour to 30 days, and can have a download limit. Names, types, true sizes and contents are inside the envelope. Recipients can keep what they open.</p><h2>Lock a file for an @name</h2><p>Use the <a href="https://github.com/OpenVibers/OpenVibe.SDK/blob/main/docs/locker.md">existing Locker SDK</a> and the Network key directory. Include your own key before uploading to Drive, then use the Share API to add the recipient.</p><pre><code>${esc(`const locker = require('openvibe-sdk/locker');
const { share } = require('openvibe-sdk/share');
// token and subject come from your signed-in OpenVibe session.