@mblaney/holster 1.0.35 → 1.0.36

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.
Files changed (67) hide show
  1. package/build/holster.js +3 -3
  2. package/build/holster.js.map +3 -3
  3. package/dist/holster.js +4 -0
  4. package/dist/holster.js.map +7 -0
  5. package/package.json +23 -5
  6. package/src/get.js +6 -18
  7. package/src/ham.js +17 -47
  8. package/src/holster.js +106 -28
  9. package/src/sea.js +0 -52
  10. package/src/store.js +7 -7
  11. package/src/wire.js +7 -1
  12. package/ts/src/array.ts +78 -0
  13. package/ts/src/buffer.ts +441 -0
  14. package/ts/src/dup.ts +49 -0
  15. package/ts/src/get.ts +83 -0
  16. package/ts/src/ham.ts +254 -0
  17. package/ts/src/holster.ts +931 -0
  18. package/ts/src/index.ts +35 -0
  19. package/ts/src/radisk.ts +585 -0
  20. package/ts/src/radix.ts +157 -0
  21. package/ts/src/schemas.ts +519 -0
  22. package/ts/src/sea-utils.ts +103 -0
  23. package/ts/src/sea.ts +388 -0
  24. package/ts/src/store.ts +321 -0
  25. package/ts/src/user.ts +381 -0
  26. package/ts/src/utils.ts +232 -0
  27. package/ts/src/wire.ts +829 -0
  28. package/.dockerignore +0 -9
  29. package/.github/workflows/node.js.yml +0 -31
  30. package/.github/workflows/npm-publish.yml +0 -33
  31. package/Dockerfile +0 -20
  32. package/examples/index.html +0 -33
  33. package/examples/index.js +0 -49
  34. package/examples/user.html +0 -31
  35. package/examples/user.js +0 -93
  36. package/examples/wire.html +0 -27
  37. package/examples/wire.js +0 -55
  38. package/test/holster.get.test.js +0 -94
  39. package/test/holster.lex.test.js +0 -196
  40. package/test/holster.off.test.js +0 -53
  41. package/test/holster.on.test.js +0 -264
  42. package/test/holster.put.test.js +0 -358
  43. package/test/holster.secure.test.js +0 -202
  44. package/test/holster.user.get.test.js +0 -114
  45. package/test/holster.user.lex.test.js +0 -149
  46. package/test/holster.user.off.test.js +0 -73
  47. package/test/holster.user.on.test.js +0 -300
  48. package/test/holster.user.put.test.js +0 -463
  49. package/test/kitty/! +0 -9
  50. package/test/names.js +0 -225
  51. package/test/radisk.cache.test.js +0 -213
  52. package/test/radisk.test.js +0 -331
  53. package/test/radix.test.js +0 -117
  54. package/test/sea.test.js +0 -284
  55. package/test/split.test.js +0 -317
  56. package/test/store.test.js +0 -316
  57. package/test/system/basic.test.js +0 -29
  58. package/test/system/chained-on.test.js +0 -41
  59. package/test/system/concurrent-listeners.test.js +0 -125
  60. package/test/system/immediate-read.test.js +0 -30
  61. package/test/system/listener-before-data.test.js +0 -51
  62. package/test/system/promise-wrapper.test.js +0 -47
  63. package/test/system/user-chained-on.test.js +0 -60
  64. package/test/system/user-concurrent-listeners.test.js +0 -142
  65. package/test/system/user-listener-before-data.test.js +0 -66
  66. package/test/user.test.js +0 -173
  67. package/test/wire.test.js +0 -158
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Store - Storage interface for graph data
3
+ * Provides get and put methods with filesystem or IndexedDB backend
4
+ */
5
+
6
+ import Radisk from "./radisk.ts"
7
+ import Radix from "./radix.ts"
8
+ import * as utils from "./utils.ts"
9
+ import type {
10
+ Lex,
11
+ Graph,
12
+ GraphNode,
13
+ StoreOptions,
14
+ FileSystemInterface,
15
+ EncodedValue,
16
+ } from "./schemas.ts"
17
+
18
+ const isNode = typeof document === "undefined"
19
+
20
+ // Dynamic import for Node.js fs module - won't execute in browser/service worker
21
+ const fs = isNode
22
+ ? await import(/*webpackIgnore: true*/ "node:fs")
23
+ : undefined
24
+
25
+ // ASCII character for enquiry
26
+ const enq = String.fromCharCode(5)
27
+ // ASCII character for unit separator
28
+ const unit = String.fromCharCode(31)
29
+ // On-disk root node format
30
+ const root = unit + "+0" + unit + "#" + unit + '"root' + unit
31
+
32
+ /**
33
+ * Create file system storage backend
34
+ */
35
+ const fileSystem = (opt: StoreOptions): FileSystemInterface => {
36
+ const dir = opt.file!
37
+
38
+ if (isNode) {
39
+ if (!fs!.existsSync(dir)) fs!.mkdirSync(dir)
40
+ if (!fs!.existsSync(dir + "/!")) fs!.writeFileSync(dir + "/!", root)
41
+
42
+ return {
43
+ get: (file, cb) => {
44
+ fs!.readFile(dir + "/" + file, (err: NodeJS.ErrnoException | null, data?: Buffer) => {
45
+ if (err) {
46
+ if ((err as { code?: string }).code === "ENOENT") {
47
+ cb()
48
+ return
49
+ }
50
+ console.log("fs.readFile error:", err)
51
+ }
52
+ const dataStr = data ? data.toString() : undefined
53
+ cb(err as never, dataStr)
54
+ })
55
+ },
56
+ put: (file, data, cb) => {
57
+ const tmp = file + "." + utils.text.random(9) + ".tmp"
58
+ fs!.writeFile(tmp, data as string, (err: NodeJS.ErrnoException | null) => {
59
+ if (err) {
60
+ console.log("fs.writeFile error:", err)
61
+ cb(err as never)
62
+ return
63
+ }
64
+ fs!.rename(tmp, dir + "/" + file, cb as never)
65
+ })
66
+ },
67
+ list: cb => {
68
+ fs!.readdir(dir, (err: NodeJS.ErrnoException | null, files?: string[]) => {
69
+ if (err) {
70
+ console.log("fs.readdir error:", err)
71
+ cb()
72
+ return
73
+ }
74
+ if (files) files.forEach(cb)
75
+ cb()
76
+ })
77
+ },
78
+ }
79
+ }
80
+
81
+ if (opt.indexedDB) {
82
+ let db: IDBDatabase
83
+ const dbReady = new Promise<void>((resolve, reject) => {
84
+ const o = indexedDB.open(dir, 1)
85
+ o.onupgradeneeded = event => {
86
+ ;(event.target as IDBOpenDBRequest).result.createObjectStore(dir)
87
+ }
88
+ o.onerror = event => {
89
+ console.log(event)
90
+ reject(event)
91
+ }
92
+ o.onsuccess = () => {
93
+ db = o.result
94
+ if (db) {
95
+ const tx = db.transaction([dir], "readonly")
96
+ const req = tx.objectStore(dir).getKey("!")
97
+ req.onerror = () => {
98
+ console.log(`error getting key ${dir}/!`)
99
+ reject(req.error)
100
+ }
101
+ req.onsuccess = () => {
102
+ if (!req.result) {
103
+ const tx = db.transaction([dir], "readwrite")
104
+ const req = tx.objectStore(dir).put(root, "!")
105
+ req.onerror = () => {
106
+ console.log(`error putting root on ${dir}/!`)
107
+ reject(req.error)
108
+ }
109
+ req.onsuccess = () => {
110
+ resolve()
111
+ }
112
+ } else {
113
+ resolve()
114
+ }
115
+ }
116
+ } else {
117
+ console.log("error indexedDB not available")
118
+ reject(new Error("indexedDB not available"))
119
+ }
120
+ }
121
+ })
122
+
123
+ return {
124
+ get: (file, cb) => {
125
+ const _get = (file: string, cb: (err?: string | null, data?: string) => void) => {
126
+ const tx = db.transaction([dir], "readonly")
127
+ const req = tx.objectStore(dir).get(file)
128
+ req.onerror = () => {
129
+ console.log(`error getting ${dir}/${file}`)
130
+ cb(req.error as unknown as string)
131
+ }
132
+ req.onsuccess = () => {
133
+ cb(null, req.result)
134
+ }
135
+ }
136
+
137
+ dbReady
138
+ .then(() => {
139
+ _get(file, cb)
140
+ })
141
+ .catch(err => {
142
+ cb(err as string)
143
+ })
144
+ },
145
+ put: (file, data, cb) => {
146
+ const _put = (
147
+ file: string,
148
+ data: string,
149
+ cb: (err?: string | null) => void
150
+ ) => {
151
+ const tx = db.transaction([dir], "readwrite")
152
+ const req = tx.objectStore(dir).put(data, file)
153
+ req.onerror = () => {
154
+ console.log(`error putting data on ${dir}/${file}`)
155
+ cb(req.error as unknown as string)
156
+ }
157
+ req.onsuccess = () => {
158
+ cb(null)
159
+ }
160
+ }
161
+
162
+ dbReady
163
+ .then(() => {
164
+ _put(file, data as string, cb)
165
+ })
166
+ .catch(err => {
167
+ cb(err as string)
168
+ })
169
+ },
170
+ list: cb => {
171
+ const _list = (cb: (file?: string) => void) => {
172
+ const tx = db.transaction([dir], "readonly")
173
+ const req = tx.objectStore(dir).getAllKeys()
174
+ req.onerror = () => {
175
+ console.log("error getting keys for", dir)
176
+ cb()
177
+ }
178
+ req.onsuccess = () => {
179
+ ;(req.result as string[]).forEach(cb)
180
+ cb()
181
+ }
182
+ }
183
+
184
+ dbReady
185
+ .then(() => {
186
+ _list(cb)
187
+ })
188
+ .catch(err => {
189
+ console.log("error indexedDB not available:", err)
190
+ cb()
191
+ })
192
+ },
193
+ }
194
+ }
195
+
196
+ // No browser storage
197
+ return {
198
+ get: (_file, cb) => {
199
+ cb(null, root)
200
+ },
201
+ put: (_file, _data, cb) => {
202
+ cb(null)
203
+ },
204
+ list: cb => {
205
+ cb("!")
206
+ cb()
207
+ },
208
+ }
209
+ }
210
+
211
+ export interface StoreInterface {
212
+ get: (
213
+ lex: Lex,
214
+ cb: (err?: string | null, graph?: Graph) => void,
215
+ _opt?: { secure?: boolean }
216
+ ) => void
217
+ put: (graph: Graph, cb: (err?: string | null) => void) => void
218
+ }
219
+
220
+ /**
221
+ * Create Store with get and put methods
222
+ */
223
+ const Store = (opt?: StoreOptions): StoreInterface => {
224
+ const options = opt || {}
225
+ options.file = String(options.file || "radata")
226
+ if (!options.store) options.store = fileSystem(options)
227
+ const radisk = Radisk(options as never)
228
+
229
+ return {
230
+ get: (lex, cb, _opt) => {
231
+ if (!lex || !utils.obj.is(lex)) {
232
+ cb("lex required")
233
+ return
234
+ }
235
+ if (!lex["#"]) {
236
+ cb("soul required in lex")
237
+ return
238
+ }
239
+
240
+ const opts = _opt || {}
241
+ const soul = lex["#"]
242
+ const key =
243
+ !opts.secure && typeof lex["."] === "string" ? lex["."] : ""
244
+ let node: GraphNode | undefined
245
+ const signatures: Record<string, string> = {}
246
+
247
+ const each = (value: EncodedValue, key: string): void => {
248
+ // Always include userPublicKey for verification, regardless of filter
249
+ if (key !== utils.userPublicKey && !utils.match(lex["."], key)) return
250
+
251
+ if (!node) node = { _: { "#": soul, ">": {} } }
252
+ node[key] = value[0]
253
+ node._[">"][key] = value[1]
254
+ // If signature is present, store it in _["s"]
255
+ if (value.length === 3 && value[2]) {
256
+ signatures[value[1].toString()] = value[2]
257
+ }
258
+ }
259
+
260
+ radisk(soul + enq + key, (err, value) => {
261
+ let graph: Graph | undefined
262
+ if (utils.obj.is(value)) {
263
+ Radix.map(value as never, each)
264
+ if (!node) each(value as never, key)
265
+ graph = { [soul]: node! }
266
+ } else if (value) {
267
+ each(value as EncodedValue, key)
268
+ graph = { [soul]: node! }
269
+ }
270
+ // Only add _["s"] if we found any signatures
271
+ if (graph && graph[soul] && Object.keys(signatures).length > 0) {
272
+ graph[soul]._["s"] = signatures
273
+ }
274
+ cb(err, graph)
275
+ })
276
+ },
277
+ put: (graph, cb) => {
278
+ if (!graph) {
279
+ cb("graph required")
280
+ return
281
+ }
282
+
283
+ let count = 0
284
+ let finished = false
285
+ const ack = (err?: string | null): void => {
286
+ count--
287
+ if (finished) return
288
+
289
+ if (err) {
290
+ finished = true
291
+ cb(err)
292
+ return
293
+ }
294
+
295
+ if (count === 0) {
296
+ finished = true
297
+ cb(null)
298
+ }
299
+ }
300
+
301
+ Object.keys(graph).forEach(soul => {
302
+ const node = graph[soul]!
303
+ Object.keys(node).forEach(key => {
304
+ if (key === "_") return
305
+
306
+ count++
307
+ const value = node[key]!
308
+ const state = node._[">"][key]!
309
+ const sig = node._["s"] && node._["s"][state]
310
+ const data: EncodedValue = sig
311
+ ? [value, state, sig]
312
+ : [value, state]
313
+ radisk(soul + enq + key, data, ack)
314
+ })
315
+ })
316
+ },
317
+ }
318
+ }
319
+
320
+ export default Store
321
+
package/ts/src/user.ts ADDED
@@ -0,0 +1,381 @@
1
+ /**
2
+ * User - User authentication and management
3
+ * Provides user creation, authentication, and password management
4
+ */
5
+
6
+ import * as utils from "./utils.ts"
7
+ import Wire from "./wire.ts"
8
+ import SEA from "./sea.ts"
9
+ import type { UserIdentity, HolsterOptions, LexFilter, WireOptions } from "./schemas.ts"
10
+ import type { HolsterAPI } from "./holster.ts"
11
+
12
+ type WireInterface = ReturnType<typeof Wire>
13
+
14
+ export interface UserInterface {
15
+ is?: UserIdentity | null
16
+ create: (username: string, password: string, cb?: (err?: string | null) => void) => void
17
+ auth: (username: string, password: string, cb?: (err?: string | null) => void) => void
18
+ change: (
19
+ username: string,
20
+ password: string,
21
+ newPassword: string,
22
+ cb?: (err?: string | null) => void
23
+ ) => void
24
+ store: (localStorage?: boolean) => void
25
+ recall: () => void
26
+ leave: () => void
27
+ delete: (username: string, password: string, cb?: (err?: string | null) => void) => void
28
+ get?(
29
+ keys: string | string[],
30
+ lex?: LexFilter | ((data: unknown) => void),
31
+ cb?: ((data: unknown) => void) | WireOptions,
32
+ _opt?: WireOptions
33
+ ): HolsterAPI
34
+ }
35
+
36
+ /**
37
+ * Create User management interface
38
+ */
39
+ const User = (opt: HolsterOptions | undefined, wire?: WireInterface): UserInterface => {
40
+ if (!wire) wire = Wire(opt as HolsterOptions)
41
+ const wait = opt?.wait ?? 5000
42
+ let pubs: string[] = []
43
+ let creating = false
44
+ let authing = false
45
+ let retries = 0
46
+
47
+ const auth = (
48
+ username: string,
49
+ password: string,
50
+ newPassword: string,
51
+ ack: (err?: string | null) => void
52
+ ): void => {
53
+ const retry = (username: string): void => {
54
+ retries++
55
+ auth(username, password, newPassword, ack)
56
+ }
57
+ const done = (err?: string | null): void => {
58
+ pubs = []
59
+ retries = 0
60
+ authing = false
61
+ ack(err)
62
+ }
63
+ const next = (): void => {
64
+ if (pubs.length === 0) {
65
+ done("Wrong username or password")
66
+ return
67
+ }
68
+
69
+ const pub = pubs.shift()!
70
+ wire.get(
71
+ { "#": pub, ".": ["auth", "pub", "epub"] },
72
+ async msg => {
73
+ if (msg.err) {
74
+ done(`error getting ${pub}: ${msg.err}`)
75
+ return
76
+ }
77
+
78
+ const data = msg.put && msg.put[pub]
79
+ if (!data || !data.auth) return next()
80
+
81
+ const authData = JSON.parse(data.auth as string) as {
82
+ enc: { ct: string; iv: string; s: string }
83
+ salt: string
84
+ }
85
+ const work = await SEA.work(password, authData.salt)
86
+ const dec = (await SEA.decrypt(authData.enc, work)) as
87
+ | { priv: string; epriv: string }
88
+ | null
89
+ if (!dec) return next()
90
+
91
+ user.is = {
92
+ username: username,
93
+ pub: data.pub as string,
94
+ epub: data.epub as string,
95
+ priv: dec.priv,
96
+ epriv: dec.epriv,
97
+ }
98
+ if (newPassword !== "") {
99
+ const salt = utils.text.random(64)
100
+ const newWork = await SEA.work(newPassword, salt)
101
+ const enc = await SEA.encrypt(dec, newWork)
102
+ const update = {
103
+ auth: JSON.stringify({ enc: enc, salt: salt }),
104
+ }
105
+ const timestamp = Date.now()
106
+ const sig = await SEA.signTimestamp(timestamp, user.is)
107
+ const graph = utils.graph(pub, update, sig!, data.pub as string, timestamp)
108
+ wire.put(graph, err => {
109
+ if (err) {
110
+ done(`error putting ${JSON.stringify(update)} on ${pub}: ${err}`)
111
+ } else {
112
+ done(null)
113
+ }
114
+ })
115
+ return
116
+ }
117
+
118
+ done(null)
119
+ },
120
+ { wait: wait }
121
+ )
122
+ }
123
+
124
+ if (retries > 9) {
125
+ done("Wrong username or password")
126
+ return
127
+ }
128
+
129
+ const soul = "~@" + username
130
+ wire.get(
131
+ { "#": soul },
132
+ async msg => {
133
+ if (msg.err) {
134
+ done(`error getting ${soul}: ${msg.err}`)
135
+ return
136
+ }
137
+
138
+ const data = msg.put && msg.put[soul]
139
+ if (!data) return retry(username)
140
+
141
+ delete (data as { _?: unknown })._
142
+ pubs = Object.keys(data)
143
+ next()
144
+ },
145
+ { wait: wait }
146
+ )
147
+ }
148
+
149
+ const user: UserInterface = {
150
+ is: null,
151
+ create: (username, password, cb) => {
152
+ const ack = (err?: string | null): void => {
153
+ if (cb) cb(err)
154
+ else if (err) console.log(err)
155
+ }
156
+
157
+ if (creating) {
158
+ ack("User is already being created")
159
+ return
160
+ }
161
+
162
+ if (username === "") {
163
+ ack("Please provide a username")
164
+ return
165
+ }
166
+
167
+ if (password === "") {
168
+ ack("Please provide a password")
169
+ return
170
+ }
171
+
172
+ creating = true
173
+
174
+ const soul = "~@" + username
175
+ wire.get(
176
+ { "#": soul },
177
+ async msg => {
178
+ if (msg.err) {
179
+ creating = false
180
+ ack(`error getting ${soul}: ${msg.err}`)
181
+ return
182
+ }
183
+
184
+ if (msg.put && msg.put[soul]) {
185
+ creating = false
186
+ ack("Username already exists")
187
+ return
188
+ }
189
+
190
+ const salt = utils.text.random(64)
191
+ const work = await SEA.work(password, salt)
192
+ const pair = await SEA.pair()
193
+ const priv = { priv: pair.priv, epriv: pair.epriv }
194
+ const enc = await SEA.encrypt(priv, work)
195
+ const data = {
196
+ username: username,
197
+ pub: pair.pub,
198
+ epub: pair.epub,
199
+ auth: JSON.stringify({ enc: enc, salt: salt }),
200
+ }
201
+
202
+ const pub = "~" + pair.pub
203
+ const timestamp = Date.now()
204
+ const sig = await SEA.signTimestamp(timestamp, pair)
205
+ const graph = utils.graph(pub, data, sig!, pair.pub, timestamp)
206
+ wire.put(graph, err => {
207
+ creating = false
208
+ if (err) {
209
+ ack(`error putting ${JSON.stringify(data)} on ${pub}: ${err}`)
210
+ return
211
+ }
212
+
213
+ const rel = { [pub]: { "#": pub } }
214
+ wire.put(utils.graph(soul, rel as never), err => {
215
+ if (err) {
216
+ ack(`error putting ${JSON.stringify(rel)} on ${soul}: ${err}`)
217
+ return
218
+ }
219
+
220
+ if (cb) cb(null)
221
+ })
222
+ })
223
+ },
224
+ { wait: wait }
225
+ )
226
+ },
227
+ auth: (username, password, cb) => {
228
+ const ack = (err?: string | null): void => {
229
+ if (cb) cb(err)
230
+ else if (err) console.log(err)
231
+ }
232
+
233
+ if (authing) {
234
+ ack("User is already authenticating")
235
+ return
236
+ }
237
+
238
+ user.is = null
239
+
240
+ if (username === "") {
241
+ ack("Please provide a username")
242
+ return
243
+ }
244
+
245
+ if (password === "") {
246
+ ack("Please provide a password")
247
+ return
248
+ }
249
+
250
+ authing = true
251
+ auth(username, password, "", ack)
252
+ },
253
+ change: (username, password, newPassword, cb) => {
254
+ const ack = (err?: string | null): void => {
255
+ if (cb) cb(err)
256
+ else if (err) console.log(err)
257
+ }
258
+
259
+ if (authing) {
260
+ ack("User is already authenticating")
261
+ return
262
+ }
263
+
264
+ user.is = null
265
+
266
+ if (username === "") {
267
+ ack("Please provide a username")
268
+ return
269
+ }
270
+
271
+ if (password === "") {
272
+ ack("Please provide a password")
273
+ return
274
+ }
275
+
276
+ if (newPassword === "") {
277
+ ack("Please provide a new password")
278
+ return
279
+ }
280
+
281
+ authing = true
282
+ auth(username, password, newPassword, ack)
283
+ },
284
+ store: (localStorage = false) => {
285
+ if (!user.is) {
286
+ console.log("Please authenticate before calling store")
287
+ return
288
+ }
289
+
290
+ if (localStorage) {
291
+ if (typeof globalThis.localStorage !== "undefined") {
292
+ globalThis.localStorage.setItem("user.is", JSON.stringify(user.is))
293
+ }
294
+ if (typeof globalThis.sessionStorage !== "undefined") {
295
+ globalThis.sessionStorage.removeItem("user.is")
296
+ }
297
+ return
298
+ }
299
+
300
+ if (typeof globalThis.sessionStorage !== "undefined") {
301
+ globalThis.sessionStorage.setItem("user.is", JSON.stringify(user.is))
302
+ }
303
+ if (typeof globalThis.localStorage !== "undefined") {
304
+ globalThis.localStorage.removeItem("user.is")
305
+ }
306
+ },
307
+ recall: () => {
308
+ if (typeof globalThis.localStorage !== "undefined") {
309
+ const is = globalThis.localStorage.getItem("user.is")
310
+ if (is) {
311
+ user.is = JSON.parse(is)
312
+ return
313
+ }
314
+ }
315
+
316
+ if (typeof globalThis.sessionStorage !== "undefined") {
317
+ const is = globalThis.sessionStorage.getItem("user.is")
318
+ if (is) {
319
+ user.is = JSON.parse(is)
320
+ }
321
+ }
322
+ },
323
+ leave: () => {
324
+ user.is = null
325
+ if (typeof globalThis.localStorage !== "undefined") {
326
+ globalThis.localStorage.removeItem("user.is")
327
+ }
328
+ if (typeof globalThis.sessionStorage !== "undefined") {
329
+ globalThis.sessionStorage.removeItem("user.is")
330
+ }
331
+ },
332
+ delete: (username, password, cb) => {
333
+ const ack = (err?: string | null): void => {
334
+ if (cb) cb(err)
335
+ else if (err) console.log(err)
336
+ }
337
+
338
+ if (authing) {
339
+ ack("User is already authenticating")
340
+ return
341
+ }
342
+
343
+ if (username === "") {
344
+ ack("Please provide a username")
345
+ return
346
+ }
347
+
348
+ if (password === "") {
349
+ ack("Please provide a password")
350
+ return
351
+ }
352
+
353
+ authing = true
354
+ auth(username, password, "", async err => {
355
+ if (err) {
356
+ ack(err)
357
+ return
358
+ }
359
+
360
+ const data = { username: null, pub: null, epub: null, auth: null }
361
+ const timestamp = Date.now()
362
+ const sig = await SEA.signTimestamp(timestamp, user.is!)
363
+ const pub = "~" + user.is!.pub
364
+ const graph = utils.graph(pub, data as never, sig!, user.is!.pub, timestamp)
365
+ wire.put(graph, err => {
366
+ if (err) {
367
+ ack(`error putting null on ${pub}: ${err}`)
368
+ return
369
+ }
370
+
371
+ user.is = null
372
+ if (cb) cb(null)
373
+ })
374
+ })
375
+ },
376
+ }
377
+ return user
378
+ }
379
+
380
+ export default User
381
+