@mblaney/holster 1.0.35 → 2.0.0

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 (72) 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 +22 -5
  6. package/src/dup.js +4 -5
  7. package/src/get.js +10 -19
  8. package/src/ham.js +22 -49
  9. package/src/holster.js +172 -58
  10. package/src/index.js +1 -1
  11. package/src/radisk.js +89 -86
  12. package/src/sea-utils.js +1 -1
  13. package/src/sea.js +0 -52
  14. package/src/store.js +8 -8
  15. package/src/user.js +0 -4
  16. package/src/wire.js +209 -79
  17. package/ts/src/array.ts +78 -0
  18. package/ts/src/buffer.ts +441 -0
  19. package/ts/src/dup.ts +38 -0
  20. package/ts/src/get.ts +83 -0
  21. package/ts/src/ham.ts +258 -0
  22. package/ts/src/holster.ts +968 -0
  23. package/ts/src/index.ts +35 -0
  24. package/ts/src/radisk.ts +591 -0
  25. package/ts/src/radix.ts +157 -0
  26. package/ts/src/schemas.ts +515 -0
  27. package/ts/src/sea-utils.ts +103 -0
  28. package/ts/src/sea.ts +388 -0
  29. package/ts/src/store.ts +321 -0
  30. package/ts/src/user.ts +377 -0
  31. package/ts/src/utils.ts +232 -0
  32. package/ts/src/wire.ts +961 -0
  33. package/.dockerignore +0 -9
  34. package/.github/workflows/node.js.yml +0 -31
  35. package/.github/workflows/npm-publish.yml +0 -33
  36. package/Dockerfile +0 -20
  37. package/examples/index.html +0 -33
  38. package/examples/index.js +0 -49
  39. package/examples/user.html +0 -31
  40. package/examples/user.js +0 -93
  41. package/examples/wire.html +0 -27
  42. package/examples/wire.js +0 -55
  43. package/test/holster.get.test.js +0 -94
  44. package/test/holster.lex.test.js +0 -196
  45. package/test/holster.off.test.js +0 -53
  46. package/test/holster.on.test.js +0 -264
  47. package/test/holster.put.test.js +0 -358
  48. package/test/holster.secure.test.js +0 -202
  49. package/test/holster.user.get.test.js +0 -114
  50. package/test/holster.user.lex.test.js +0 -149
  51. package/test/holster.user.off.test.js +0 -73
  52. package/test/holster.user.on.test.js +0 -300
  53. package/test/holster.user.put.test.js +0 -463
  54. package/test/kitty/! +0 -9
  55. package/test/names.js +0 -225
  56. package/test/radisk.cache.test.js +0 -213
  57. package/test/radisk.test.js +0 -331
  58. package/test/radix.test.js +0 -117
  59. package/test/sea.test.js +0 -284
  60. package/test/split.test.js +0 -317
  61. package/test/store.test.js +0 -316
  62. package/test/system/basic.test.js +0 -29
  63. package/test/system/chained-on.test.js +0 -41
  64. package/test/system/concurrent-listeners.test.js +0 -125
  65. package/test/system/immediate-read.test.js +0 -30
  66. package/test/system/listener-before-data.test.js +0 -51
  67. package/test/system/promise-wrapper.test.js +0 -47
  68. package/test/system/user-chained-on.test.js +0 -60
  69. package/test/system/user-concurrent-listeners.test.js +0 -142
  70. package/test/system/user-listener-before-data.test.js +0 -66
  71. package/test/user.test.js +0 -173
  72. 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 process !== "undefined" && process.versions?.node != null
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,377 @@
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
+ let pubs: string[] = []
42
+ let creating = false
43
+ let authing = false
44
+ let retries = 0
45
+
46
+ const auth = (
47
+ username: string,
48
+ password: string,
49
+ newPassword: string,
50
+ ack: (err?: string | null) => void
51
+ ): void => {
52
+ const retry = (username: string): void => {
53
+ retries++
54
+ auth(username, password, newPassword, ack)
55
+ }
56
+ const done = (err?: string | null): void => {
57
+ pubs = []
58
+ retries = 0
59
+ authing = false
60
+ ack(err)
61
+ }
62
+ const next = (): void => {
63
+ if (pubs.length === 0) {
64
+ done("Wrong username or password")
65
+ return
66
+ }
67
+
68
+ const pub = pubs.shift()!
69
+ wire.get(
70
+ { "#": pub, ".": ["auth", "pub", "epub"] },
71
+ async msg => {
72
+ if (msg.err) {
73
+ done(`error getting ${pub}: ${msg.err}`)
74
+ return
75
+ }
76
+
77
+ const data = msg.put && msg.put[pub]
78
+ if (!data || !data.auth) return next()
79
+
80
+ const authData = JSON.parse(data.auth as string) as {
81
+ enc: { ct: string; iv: string; s: string }
82
+ salt: string
83
+ }
84
+ const work = await SEA.work(password, authData.salt)
85
+ const dec = (await SEA.decrypt(authData.enc, work)) as
86
+ | { priv: string; epriv: string }
87
+ | null
88
+ if (!dec) return next()
89
+
90
+ user.is = {
91
+ username: username,
92
+ pub: data.pub as string,
93
+ epub: data.epub as string,
94
+ priv: dec.priv,
95
+ epriv: dec.epriv,
96
+ }
97
+ if (newPassword !== "") {
98
+ const salt = utils.text.random(64)
99
+ const newWork = await SEA.work(newPassword, salt)
100
+ const enc = await SEA.encrypt(dec, newWork)
101
+ const update = {
102
+ auth: JSON.stringify({ enc: enc, salt: salt }),
103
+ }
104
+ const timestamp = Date.now()
105
+ const sig = await SEA.signTimestamp(timestamp, user.is)
106
+ const graph = utils.graph(pub, update, sig!, data.pub as string, timestamp)
107
+ wire.put(graph, err => {
108
+ if (err) {
109
+ done(`error putting ${JSON.stringify(update)} on ${pub}: ${err}`)
110
+ } else {
111
+ done(null)
112
+ }
113
+ })
114
+ return
115
+ }
116
+
117
+ done(null)
118
+ },
119
+ )
120
+ }
121
+
122
+ if (retries > 9) {
123
+ done("Wrong username or password")
124
+ return
125
+ }
126
+
127
+ const soul = "~@" + username
128
+ wire.get(
129
+ { "#": soul },
130
+ async msg => {
131
+ if (msg.err) {
132
+ done(`error getting ${soul}: ${msg.err}`)
133
+ return
134
+ }
135
+
136
+ const data = msg.put && msg.put[soul]
137
+ if (!data) return retry(username)
138
+
139
+ delete (data as { _?: unknown })._
140
+ pubs = Object.keys(data)
141
+ next()
142
+ },
143
+ )
144
+ }
145
+
146
+ const user: UserInterface = {
147
+ is: null,
148
+ create: (username, password, cb) => {
149
+ const ack = (err?: string | null): void => {
150
+ if (cb) cb(err)
151
+ else if (err) console.log(err)
152
+ }
153
+
154
+ if (creating) {
155
+ ack("User is already being created")
156
+ return
157
+ }
158
+
159
+ if (username === "") {
160
+ ack("Please provide a username")
161
+ return
162
+ }
163
+
164
+ if (password === "") {
165
+ ack("Please provide a password")
166
+ return
167
+ }
168
+
169
+ creating = true
170
+
171
+ const soul = "~@" + username
172
+ wire.get(
173
+ { "#": soul },
174
+ async msg => {
175
+ if (msg.err) {
176
+ creating = false
177
+ ack(`error getting ${soul}: ${msg.err}`)
178
+ return
179
+ }
180
+
181
+ if (msg.put && msg.put[soul]) {
182
+ creating = false
183
+ ack("Username already exists")
184
+ return
185
+ }
186
+
187
+ const salt = utils.text.random(64)
188
+ const work = await SEA.work(password, salt)
189
+ const pair = await SEA.pair()
190
+ const priv = { priv: pair.priv, epriv: pair.epriv }
191
+ const enc = await SEA.encrypt(priv, work)
192
+ const data = {
193
+ username: username,
194
+ pub: pair.pub,
195
+ epub: pair.epub,
196
+ auth: JSON.stringify({ enc: enc, salt: salt }),
197
+ }
198
+
199
+ const pub = "~" + pair.pub
200
+ const timestamp = Date.now()
201
+ const sig = await SEA.signTimestamp(timestamp, pair)
202
+ const graph = utils.graph(pub, data, sig!, pair.pub, timestamp)
203
+ wire.put(graph, err => {
204
+ creating = false
205
+ if (err) {
206
+ ack(`error putting ${JSON.stringify(data)} on ${pub}: ${err}`)
207
+ return
208
+ }
209
+
210
+ const rel = { [pub]: { "#": pub } }
211
+ wire.put(utils.graph(soul, rel as never), err => {
212
+ if (err) {
213
+ ack(`error putting ${JSON.stringify(rel)} on ${soul}: ${err}`)
214
+ return
215
+ }
216
+
217
+ if (cb) cb(null)
218
+ })
219
+ })
220
+ },
221
+ )
222
+ },
223
+ auth: (username, password, cb) => {
224
+ const ack = (err?: string | null): void => {
225
+ if (cb) cb(err)
226
+ else if (err) console.log(err)
227
+ }
228
+
229
+ if (authing) {
230
+ ack("User is already authenticating")
231
+ return
232
+ }
233
+
234
+ user.is = null
235
+
236
+ if (username === "") {
237
+ ack("Please provide a username")
238
+ return
239
+ }
240
+
241
+ if (password === "") {
242
+ ack("Please provide a password")
243
+ return
244
+ }
245
+
246
+ authing = true
247
+ auth(username, password, "", ack)
248
+ },
249
+ change: (username, password, newPassword, cb) => {
250
+ const ack = (err?: string | null): void => {
251
+ if (cb) cb(err)
252
+ else if (err) console.log(err)
253
+ }
254
+
255
+ if (authing) {
256
+ ack("User is already authenticating")
257
+ return
258
+ }
259
+
260
+ user.is = null
261
+
262
+ if (username === "") {
263
+ ack("Please provide a username")
264
+ return
265
+ }
266
+
267
+ if (password === "") {
268
+ ack("Please provide a password")
269
+ return
270
+ }
271
+
272
+ if (newPassword === "") {
273
+ ack("Please provide a new password")
274
+ return
275
+ }
276
+
277
+ authing = true
278
+ auth(username, password, newPassword, ack)
279
+ },
280
+ store: (localStorage = false) => {
281
+ if (!user.is) {
282
+ console.log("Please authenticate before calling store")
283
+ return
284
+ }
285
+
286
+ if (localStorage) {
287
+ if (typeof globalThis.localStorage !== "undefined") {
288
+ globalThis.localStorage.setItem("user.is", JSON.stringify(user.is))
289
+ }
290
+ if (typeof globalThis.sessionStorage !== "undefined") {
291
+ globalThis.sessionStorage.removeItem("user.is")
292
+ }
293
+ return
294
+ }
295
+
296
+ if (typeof globalThis.sessionStorage !== "undefined") {
297
+ globalThis.sessionStorage.setItem("user.is", JSON.stringify(user.is))
298
+ }
299
+ if (typeof globalThis.localStorage !== "undefined") {
300
+ globalThis.localStorage.removeItem("user.is")
301
+ }
302
+ },
303
+ recall: () => {
304
+ if (typeof globalThis.localStorage !== "undefined") {
305
+ const is = globalThis.localStorage.getItem("user.is")
306
+ if (is) {
307
+ user.is = JSON.parse(is)
308
+ return
309
+ }
310
+ }
311
+
312
+ if (typeof globalThis.sessionStorage !== "undefined") {
313
+ const is = globalThis.sessionStorage.getItem("user.is")
314
+ if (is) {
315
+ user.is = JSON.parse(is)
316
+ }
317
+ }
318
+ },
319
+ leave: () => {
320
+ user.is = null
321
+ if (typeof globalThis.localStorage !== "undefined") {
322
+ globalThis.localStorage.removeItem("user.is")
323
+ }
324
+ if (typeof globalThis.sessionStorage !== "undefined") {
325
+ globalThis.sessionStorage.removeItem("user.is")
326
+ }
327
+ },
328
+ delete: (username, password, cb) => {
329
+ const ack = (err?: string | null): void => {
330
+ if (cb) cb(err)
331
+ else if (err) console.log(err)
332
+ }
333
+
334
+ if (authing) {
335
+ ack("User is already authenticating")
336
+ return
337
+ }
338
+
339
+ if (username === "") {
340
+ ack("Please provide a username")
341
+ return
342
+ }
343
+
344
+ if (password === "") {
345
+ ack("Please provide a password")
346
+ return
347
+ }
348
+
349
+ authing = true
350
+ auth(username, password, "", async err => {
351
+ if (err) {
352
+ ack(err)
353
+ return
354
+ }
355
+
356
+ const data = { username: null, pub: null, epub: null, auth: null }
357
+ const timestamp = Date.now()
358
+ const sig = await SEA.signTimestamp(timestamp, user.is!)
359
+ const pub = "~" + user.is!.pub
360
+ const graph = utils.graph(pub, data as never, sig!, user.is!.pub, timestamp)
361
+ wire.put(graph, err => {
362
+ if (err) {
363
+ ack(`error putting null on ${pub}: ${err}`)
364
+ return
365
+ }
366
+
367
+ user.is = null
368
+ if (cb) cb(null)
369
+ })
370
+ })
371
+ },
372
+ }
373
+ return user
374
+ }
375
+
376
+ export default User
377
+