braid-text 0.2.51 → 0.2.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -114,7 +114,7 @@ braid_text.serve = async (req, res, options = {}) => {
114
114
  res.setHeader("Current-Version", get_current_version())
115
115
  res.setHeader("Version", ascii_ify(x.version.map((x) => JSON.stringify(x)).join(", ")))
116
116
  var buffer = Buffer.from(x.body, "utf8")
117
- res.setHeader("Repr-Digest", `sha-256=:${require('crypto').createHash('sha256').update(buffer).digest('base64')}:`)
117
+ res.setHeader("Repr-Digest", get_digest(buffer))
118
118
  res.setHeader("Content-Length", buffer.length)
119
119
  return my_end(200, req.method === "HEAD" ? null : buffer)
120
120
  }
@@ -136,10 +136,8 @@ braid_text.serve = async (req, res, options = {}) => {
136
136
 
137
137
  // this is a sanity/rhobustness check..
138
138
  // ..this digest is checked on the client..
139
- // ..it is not strictly necessary
140
- if (x.version && v_eq(x.version, resource.version)) {
141
- x["Repr-Digest"] = `sha-256=:${require('crypto').createHash('sha256').update(Buffer.from(resource.val, "utf8")).digest('base64')}:`
142
- }
139
+ if (x.version && v_eq(x.version, resource.version))
140
+ x["Repr-Digest"] = get_digest(resource.val)
143
141
 
144
142
  res.sendVersion(x)
145
143
  },
@@ -211,6 +209,19 @@ braid_text.serve = async (req, res, options = {}) => {
211
209
 
212
210
  var {change_count} = await braid_text.put(resource, { peer, version: req.version, parents: req.parents, patches, body, merge_type })
213
211
 
212
+ // if Repr-Digest is set,
213
+ // and the request version is also our new current version,
214
+ // then verify the digest..
215
+ if (req.headers['repr-digest'] &&
216
+ v_eq(req.version, resource.version) &&
217
+ req.headers['repr-digest'] !== get_digest(resource.val)) {
218
+ console.log(`repr-digest mismatch!`)
219
+
220
+ // we return a special 550 error code,
221
+ // which simpleton will pick up on to stop retrying
222
+ return done_my_turn(550, "repr-digest mismatch!")
223
+ }
224
+
214
225
  if (req.version) got_event(options.key, req.version[0], change_count)
215
226
 
216
227
  res.setHeader("Version", get_current_version())
@@ -2098,6 +2109,11 @@ function sorted_set_delete(arr, val) {
2098
2109
  if (arr[i] === val) arr.splice(i, 1)
2099
2110
  }
2100
2111
 
2112
+ function get_digest(s) {
2113
+ if (typeof s === 'string') s = Buffer.from(s, "utf8")
2114
+ return `sha-256=:${require('crypto').createHash('sha256').update(s).digest('base64')}:`
2115
+ }
2116
+
2101
2117
  braid_text.get_resource = get_resource
2102
2118
 
2103
2119
  braid_text.encode_filename = encode_filename
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-text",
3
- "version": "0.2.51",
3
+ "version": "0.2.53",
4
4
  "description": "Library for collaborative text over http using braid.",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braid-text",
@@ -65,7 +65,7 @@ function simpleton_client(url, {
65
65
  signal: ac.signal
66
66
  }).then(res => {
67
67
  if (on_res) on_res(res)
68
- res.subscribe(update => {
68
+ res.subscribe(async update => {
69
69
  // Only accept the update if its parents == our current version
70
70
  update.parents.sort()
71
71
  if (current_version.length === update.parents.length
@@ -110,6 +110,14 @@ function simpleton_client(url, {
110
110
  } else prev_state = apply_patches(prev_state, patches)
111
111
  }
112
112
 
113
+ // if the server gave us a digest,
114
+ // go ahead and check it against our new state..
115
+ if (update.extra_headers &&
116
+ update.extra_headers["repr-digest"] &&
117
+ update.extra_headers["repr-digest"].startsWith('sha-256=') &&
118
+ update.extra_headers["repr-digest"] !== await get_digest(prev_state))
119
+ throw new Error('repr-digest mismatch')
120
+
113
121
  if (on_state) on_state(prev_state)
114
122
  }
115
123
  }, on_error)
@@ -166,10 +174,13 @@ function simpleton_client(url, {
166
174
  outstanding_changes++
167
175
  try {
168
176
  var r = await braid_fetch(url, {
169
- headers: { "Merge-Type": "simpleton",
170
- ...(content_type ? {"Content-Type": content_type} : {}) },
177
+ headers: {
178
+ "Merge-Type": "simpleton",
179
+ "Repr-Digest": await get_digest(prev_state),
180
+ ...(content_type ? {"Content-Type": content_type} : {})
181
+ },
171
182
  method: "PUT",
172
- retry: () => true,
183
+ retry: (res) => res.status !== 550,
173
184
  version, parents, patches,
174
185
  peer
175
186
  })
@@ -220,4 +231,9 @@ function simpleton_client(url, {
220
231
  }
221
232
  return state
222
233
  }
234
+
235
+ async function get_digest(s) {
236
+ var bytes = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(s))
237
+ return `sha-256=:${btoa(String.fromCharCode(...new Uint8Array(bytes)))}:`
238
+ }
223
239
  }
package/test/test.html CHANGED
@@ -96,6 +96,56 @@ async function runTest(testName, testFunction, expectedResult) {
96
96
  }
97
97
  }
98
98
 
99
+ runTest(
100
+ "test PUT digest (good)",
101
+ async () => {
102
+ let key = 'test-' + Math.random().toString(36).slice(2)
103
+
104
+ async function get_digest(s) {
105
+ var bytes = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(s))
106
+ return `sha-256=:${btoa(String.fromCharCode(...new Uint8Array(bytes)))}:`
107
+ }
108
+
109
+ let r = await braid_fetch(`/${key}`, {
110
+ method: 'PUT',
111
+ version: ['hi-1'],
112
+ parents: [],
113
+ body: 'xx',
114
+ headers: {
115
+ 'Repr-Digest': await get_digest('xx')
116
+ }
117
+ })
118
+ if (!r.ok) return 'got: ' + r.status
119
+ return 'ok'
120
+ },
121
+ 'ok'
122
+ )
123
+
124
+ runTest(
125
+ "test PUT digest (bad)",
126
+ async () => {
127
+ let key = 'test-' + Math.random().toString(36).slice(2)
128
+
129
+ async function get_digest(s) {
130
+ var bytes = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(s))
131
+ return `sha-256=:${btoa(String.fromCharCode(...new Uint8Array(bytes)))}:`
132
+ }
133
+
134
+ let r = await braid_fetch(`/${key}`, {
135
+ method: 'PUT',
136
+ version: ['hi-1'],
137
+ parents: [],
138
+ body: 'xx',
139
+ headers: {
140
+ 'Repr-Digest': await get_digest('yy')
141
+ }
142
+ })
143
+ if (!r.ok) return 'got: ' + r.status
144
+ return 'ok'
145
+ },
146
+ 'got: 550'
147
+ )
148
+
99
149
  runTest(
100
150
  "test subscribing and verifying digests [simpleton]",
101
151
  async () => {
@@ -157,9 +207,6 @@ runTest(
157
207
  var parts = []
158
208
  var p = new Promise(async (done, fail) => {
159
209
  r2.subscribe(update => {
160
-
161
- console.log(`update: ${JSON.stringify(update, null, 4)}`)
162
-
163
210
  parts.push(update.extra_headers['repr-digest'])
164
211
  if (parts.length > 1) done()
165
212
  }, fail)