secure-scuttlebot classic

remove some sort of frontend ai generated that was not being used

Changed files
+20 -452
lib
public
+10
README.md
··· 133 133 - **http://localhost:8989/** 134 134 135 135 The server will serve the Patchbay Lite UI and expose the necessary endpoints for the client. You can now browse and publish messages from your browser. 136 + If you need a different host/port, add this to `~/.ssb/config`: 137 + 138 + ```json 139 + { 140 + "ws": { 141 + "host": "127.0.0.1", 142 + "port": 8989 143 + } 144 + } 145 + ``` 136 146 137 147 ## Decent Bundle 138 148
+6
bin.js
··· 136 136 var Config = require('ssb-config/inject') 137 137 var config = Config(process.env.ssb_appname, overrides) 138 138 139 + if (config.ws !== false) { 140 + if (!config.ws || typeof config.ws !== 'object') config.ws = {} 141 + if (typeof config.ws.port !== 'number') config.ws.port = 8989 142 + if (typeof config.ws.host !== 'string') config.ws.host = '127.0.0.1' 143 + } 144 + 139 145 if (config.keys.curve === 'k256') 140 146 throw new Error('k256 curves are no longer supported,'+ 141 147 'please delete' + path.join(config.path, 'secret'))
+4 -65
lib/frontend.js
··· 11 11 if (!sbot.ws || typeof sbot.ws.use !== 'function') 12 12 return {} 13 13 14 - var publicDir = path.join(__dirname, '..', 'public') 15 14 var patchbayDir = path.join(__dirname, '..', 'patchbay') 16 15 var patchbayIndex = path.join(patchbayDir, 'build', 'index.html') 17 16 var hasPatchbay = fs.existsSync(patchbayIndex) ··· 38 37 sbot.ws.use(function (req, res, next) { 39 38 var url = req.url.split('?')[0] 40 39 41 - if (req.method === 'POST' && url === '/publish') { 42 - if (!sbot.add) 43 - return next() 44 - 45 - var body = '' 46 - req.on('data', function (data) { 47 - body += data 48 - if (body.length > 1e6) 49 - req.connection.destroy() 50 - }) 51 - req.on('end', function () { 52 - var data 53 - try { data = JSON.parse(body) } 54 - catch (e) { 55 - res.writeHead(400, {'Content-Type': 'application/json'}) 56 - return res.end(JSON.stringify({error: 'invalid json'})) 57 - } 58 - 59 - var msg = data && data.msg 60 - if (!msg || typeof msg !== 'object') { 61 - res.writeHead(400, {'Content-Type': 'application/json'}) 62 - return res.end(JSON.stringify({error: 'missing msg'})) 63 - } 64 - 65 - sbot.add(msg, function (err, saved) { 66 - if (err) { 67 - res.writeHead(500, {'Content-Type': 'application/json'}) 68 - return res.end(JSON.stringify({error: err.message})) 69 - } 70 - res.writeHead(200, {'Content-Type': 'application/json'}) 71 - res.end(JSON.stringify(saved, null, 2)) 72 - }) 73 - }) 74 - return 75 - } 76 - 77 40 if (req.method === 'OPTIONS' && url === '/blobs/add') { 78 41 res.writeHead(204, { 79 42 'Access-Control-Allow-Origin': '*', ··· 159 122 }) 160 123 } 161 124 162 - if (url === '/log.json') { 163 - if (!sbot.createLogStream) 164 - return next() 165 - 166 - res.writeHead(200, {'Content-Type': 'application/json'}) 167 - return pull( 168 - sbot.createLogStream({ limit: 100, reverse: true }), 169 - pull.collect(function (err, msgs) { 170 - if (err) { 171 - res.statusCode = 500 172 - return res.end(JSON.stringify({ error: err.message })) 173 - } 174 - res.end(JSON.stringify(msgs, null, 2)) 175 - }) 176 - ) 177 - } 178 - 179 125 if (url === '/') url = '/index.html' 180 126 181 127 var relPath = url.replace(/^\/+/, '') 182 - var filePath = path.join(publicDir, relPath) 183 128 var patchbayPath = path.join(patchbayDir, relPath) 184 129 185 - if (filePath.indexOf(publicDir) !== 0 || relPath.indexOf('..') !== -1) 186 - filePath = null 187 130 if (patchbayPath.indexOf(patchbayDir) !== 0 || relPath.indexOf('..') !== -1) 188 131 patchbayPath = null 189 132 190 - fs.stat(filePath || patchbayPath, function (err, stat) { 191 - if (!err && stat && stat.isFile() && filePath) return serveFile(res, filePath) 192 - 193 - if (!patchbayPath) return next() 133 + if (!patchbayPath) return next() 194 134 195 - fs.stat(patchbayPath, function (err2, stat2) { 196 - if (err2 || !stat2 || !stat2.isFile()) return next() 197 - serveFile(res, patchbayPath) 198 - }) 135 + fs.stat(patchbayPath, function (err2, stat2) { 136 + if (err2 || !stat2 || !stat2.isFile()) return next() 137 + serveFile(res, patchbayPath) 199 138 }) 200 139 }) 201 140
-303
public/app.js
··· 1 - ;(function () { 2 - var statusEl = document.getElementById('status') 3 - var keysEl = document.getElementById('keys') 4 - var feedEl = document.getElementById('feed') 5 - var composeTextEl = document.getElementById('compose-text') 6 - var composeSendEl = document.getElementById('compose-send') 7 - 8 - var currentKeys = null 9 - var currentFeedId = null 10 - var signKeyPromise = null 11 - var feedState = {} 12 - 13 - function setStatus (msg) { 14 - if (statusEl) statusEl.textContent = msg 15 - } 16 - 17 - function setKeys (obj) { 18 - if (!keysEl) return 19 - keysEl.textContent = JSON.stringify(obj, null, 2) 20 - } 21 - 22 - function clearFeed () { 23 - if (!feedEl) return 24 - while (feedEl.firstChild) feedEl.removeChild(feedEl.firstChild) 25 - } 26 - 27 - function loadKeys () { 28 - try { 29 - var raw = localStorage.getItem('ssb_browser_keys') 30 - if (!raw) return null 31 - return JSON.parse(raw) 32 - } catch (err) { 33 - console.error('failed to parse stored keys', err) 34 - return null 35 - } 36 - } 37 - 38 - function saveKeys (keys) { 39 - try { 40 - localStorage.setItem('ssb_browser_keys', JSON.stringify(keys)) 41 - } catch (err) { 42 - console.error('failed to persist keys', err) 43 - } 44 - } 45 - 46 - function toBase64 (arr) { 47 - var s = '' 48 - for (var i = 0; i < arr.length; i++) { 49 - s += String.fromCharCode(arr[i]) 50 - } 51 - return btoa(s) 52 - } 53 - 54 - function fromBase64 (str) { 55 - var bin = atob(str) 56 - var len = bin.length 57 - var arr = new Uint8Array(len) 58 - for (var i = 0; i < len; i++) { 59 - arr[i] = bin.charCodeAt(i) 60 - } 61 - return arr 62 - } 63 - 64 - function generateKeysWithWebCrypto () { 65 - if (!window.crypto || !window.crypto.subtle || !window.crypto.subtle.generateKey) { 66 - return Promise.reject(new Error('WebCrypto Ed25519 not available')) 67 - } 68 - 69 - // This assumes a modern browser with Ed25519 in SubtleCrypto. 70 - // You may need to adjust algorithm name depending on engine. 71 - return window.crypto.subtle.generateKey( 72 - { name: 'Ed25519' }, 73 - true, 74 - ['sign', 'verify'] 75 - ).then(function (keyPair) { 76 - return Promise.all([ 77 - window.crypto.subtle.exportKey('raw', keyPair.publicKey), 78 - window.crypto.subtle.exportKey('pkcs8', keyPair.privateKey) 79 - ]).then(function (exported) { 80 - var pub = new Uint8Array(exported[0]) 81 - var priv = new Uint8Array(exported[1]) 82 - 83 - // These are raw Ed25519 key bytes (pub) and PKCS#8 (priv), base64-encoded. 84 - return { 85 - curve: 'ed25519', 86 - public: toBase64(pub), 87 - private: toBase64(priv) 88 - } 89 - }) 90 - }) 91 - } 92 - 93 - function ensureKeys () { 94 - var existing = loadKeys() 95 - if (existing && existing.public && existing.private) { 96 - setStatus('Loaded existing keys from localStorage') 97 - setKeys(existing) 98 - currentKeys = existing 99 - currentFeedId = '@' + existing.public + '.ed25519' 100 - return ensureSignKey(existing).then(function () { 101 - return existing 102 - }) 103 - } 104 - 105 - setStatus('Generating new keypair in browser…') 106 - return generateKeysWithWebCrypto().then(function (keys) { 107 - saveKeys(keys) 108 - setStatus('Generated and stored new keypair') 109 - setKeys(keys) 110 - currentKeys = keys 111 - currentFeedId = '@' + keys.public + '.ed25519' 112 - return ensureSignKey(keys).then(function () { 113 - return keys 114 - }) 115 - }).catch(function (err) { 116 - console.error('failed to generate keys', err) 117 - setStatus('Failed to generate keys: ' + err.message) 118 - setKeys({}) 119 - return null 120 - }) 121 - } 122 - 123 - function ensureSignKey (keys) { 124 - if (signKeyPromise) return signKeyPromise 125 - if (!window.crypto || !window.crypto.subtle || !window.crypto.subtle.importKey) { 126 - return Promise.reject(new Error('WebCrypto Ed25519 not available')) 127 - } 128 - 129 - var privB64 = keys.private 130 - var pkcs8 = fromBase64(privB64).buffer 131 - 132 - signKeyPromise = window.crypto.subtle.importKey( 133 - 'pkcs8', 134 - pkcs8, 135 - { name: 'Ed25519' }, 136 - false, 137 - ['sign'] 138 - ) 139 - 140 - return signKeyPromise 141 - } 142 - 143 - function loadLog () { 144 - setStatus('Keys ready; loading log…') 145 - 146 - fetch('/log.json') 147 - .then(function (res) { return res.json() }) 148 - .then(function (msgs) { 149 - if (!Array.isArray(msgs)) msgs = [] 150 - feedState = {} 151 - clearFeed() 152 - 153 - // messages are newest-first (reverse: true), so first seen is latest 154 - msgs.forEach(function (msg) { 155 - var value = msg && msg.value 156 - var author = value && value.author 157 - if (!author) return 158 - if (!feedState[author]) { 159 - feedState[author] = { 160 - id: msg.key, 161 - sequence: value.sequence, 162 - timestamp: value.timestamp 163 - } 164 - } 165 - 166 - if (!feedEl || !value || !value.content) return 167 - 168 - var text = value.content && value.content.text 169 - var type = value.content && value.content.type 170 - var ts = value.timestamp 171 - var date = ts ? new Date(ts).toISOString() : '' 172 - 173 - var postDiv = document.createElement('div') 174 - postDiv.className = 'post' 175 - 176 - var metaDiv = document.createElement('div') 177 - metaDiv.className = 'post-meta' 178 - metaDiv.textContent = (type || 'message') + ' · ' + (author || '') + (date ? ' · ' + date : '') 179 - 180 - var textDiv = document.createElement('div') 181 - textDiv.className = 'post-text' 182 - textDiv.textContent = text || JSON.stringify(value.content) 183 - 184 - postDiv.appendChild(metaDiv) 185 - postDiv.appendChild(textDiv) 186 - feedEl.appendChild(postDiv) 187 - }) 188 - setStatus('Keys ready; log loaded') 189 - }) 190 - .catch(function (err) { 191 - console.error('failed to load log', err) 192 - clearFeed() 193 - if (feedEl) { 194 - var errDiv = document.createElement('div') 195 - errDiv.className = 'post' 196 - errDiv.textContent = 'failed to load log: ' + err.message 197 - feedEl.appendChild(errDiv) 198 - } 199 - setStatus('Keys ready; failed to load log') 200 - }) 201 - } 202 - 203 - function signMessage (unsignedMsg) { 204 - return ensureSignKey(currentKeys).then(function (key) { 205 - var json = JSON.stringify(unsignedMsg, null, 2) 206 - var encoder = new TextEncoder() 207 - var bytes = encoder.encode(json) 208 - return window.crypto.subtle.sign( 209 - { name: 'Ed25519' }, 210 - key, 211 - bytes 212 - ).then(function (sigBuf) { 213 - var sigBytes = new Uint8Array(sigBuf) 214 - var sigB64 = toBase64(sigBytes) 215 - return sigB64 + '.sig.ed25519' 216 - }) 217 - }) 218 - } 219 - 220 - function buildUnsignedMessage (text) { 221 - if (!currentKeys || !currentFeedId) { 222 - throw new Error('Browser keys not ready') 223 - } 224 - 225 - var state = feedState[currentFeedId] || null 226 - var ts = Date.now() 227 - if (state && ts <= state.timestamp) ts = state.timestamp + 1 228 - 229 - return { 230 - previous: state ? state.id : null, 231 - sequence: state ? state.sequence + 1 : 1, 232 - author: currentFeedId, 233 - timestamp: ts, 234 - hash: 'sha256', 235 - content: { 236 - type: 'post', 237 - text: text 238 - } 239 - } 240 - } 241 - 242 - function publishFromBrowser (text) { 243 - var unsigned = buildUnsignedMessage(text) 244 - 245 - return signMessage(unsigned).then(function (sig) { 246 - var msg = { 247 - previous: unsigned.previous, 248 - sequence: unsigned.sequence, 249 - author: unsigned.author, 250 - timestamp: unsigned.timestamp, 251 - hash: unsigned.hash, 252 - content: unsigned.content, 253 - signature: sig 254 - } 255 - 256 - return fetch('/publish', { 257 - method: 'POST', 258 - headers: { 259 - 'Content-Type': 'application/json' 260 - }, 261 - body: JSON.stringify({ msg: msg }) 262 - }).then(function (res) { 263 - return res.json() 264 - }).then(function (saved) { 265 - return saved 266 - }) 267 - }) 268 - } 269 - 270 - function wireCompose () { 271 - if (!composeSendEl || !composeTextEl) return 272 - 273 - composeSendEl.addEventListener('click', function () { 274 - var text = composeTextEl.value 275 - if (!text) return 276 - 277 - composeSendEl.disabled = true 278 - setStatus('Publishing post…') 279 - 280 - publishFromBrowser(text) 281 - .then(function (msg) { 282 - composeTextEl.value = '' 283 - setStatus('Post published; refreshing log…') 284 - loadLog() 285 - }) 286 - .catch(function (err) { 287 - console.error('failed to publish', err) 288 - setStatus('Failed to publish: ' + err.message) 289 - }) 290 - .then(function () { 291 - composeSendEl.disabled = false 292 - }) 293 - }) 294 - } 295 - 296 - // Entry point 297 - ensureKeys().then(function () { 298 - loadLog() 299 - wireCompose() 300 - // TODO: wire these keys into a browserified ssb-client 301 - // that connects to the ssb-ws endpoint exposed by ssb-server. 302 - }) 303 - })()
-84
public/index.html
··· 1 - <!doctype html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="utf-8"> 5 - <title>Patchbay Lite (ssb-server)</title> 6 - <meta name="viewport" content="width=device-width, initial-scale=1"> 7 - <style> 8 - body { 9 - font-family: sans-serif; 10 - margin: 0; 11 - padding: 0; 12 - } 13 - header { 14 - padding: .5em 1em; 15 - background: #f5f5f5; 16 - border-bottom: 1px solid #ddd; 17 - } 18 - #status { 19 - font-size: .9em; 20 - color: #666; 21 - } 22 - main { 23 - display: flex; 24 - flex-direction: column; 25 - padding: 1em; 26 - } 27 - .compose { 28 - margin-bottom: 1em; 29 - } 30 - .compose textarea { 31 - width: 100%; 32 - box-sizing: border-box; 33 - } 34 - .feed { 35 - border-top: 1px solid #eee; 36 - } 37 - .post { 38 - border-bottom: 1px solid #f5f5f5; 39 - padding: .5em 0; 40 - } 41 - .post-meta { 42 - font-size: .8em; 43 - color: #888; 44 - margin-bottom: .25em; 45 - } 46 - .post-text { 47 - white-space: pre-wrap; 48 - word-wrap: break-word; 49 - } 50 - #keys { 51 - font-size: .7em; 52 - white-space: pre-wrap; 53 - word-wrap: break-word; 54 - color: #999; 55 - } 56 - </style> 57 - </head> 58 - <body> 59 - <header> 60 - <h1>Patchbay Lite</h1> 61 - <p id="status">Loading…</p> 62 - </header> 63 - <main> 64 - <section class="compose"> 65 - <h2>Compose</h2> 66 - <textarea id="compose-text" rows="4" cols="40" placeholder="Write a post..."></textarea> 67 - <br> 68 - <button id="compose-send">Publish</button> 69 - </section> 70 - 71 - <section class="feed"> 72 - <h2>Recent log</h2> 73 - <div id="feed"></div> 74 - </section> 75 - 76 - <section> 77 - <h3>Browser keys</h3> 78 - <pre id="keys"></pre> 79 - </section> 80 - </main> 81 - 82 - <script src="app.js"></script> 83 - </body> 84 - </html>