management.mx raw
1 package main
2
3 import (
4 "smesh.lol/web/common/helpers"
5 "smesh.lol/web/common/jsbridge/ext"
6 "smesh.lol/web/common/jsbridge/schnorr"
7 "smesh.lol/web/common/jsbridge/subtle"
8 )
9
10 // Management API handlers (sm3sh modal → extension).
11
12 func mgmtGetVaultStatus() string {
13 if !vaultExists {
14 return "{\"result\":\"none\"}"
15 }
16 if vaultOpen {
17 return "{\"result\":\"unlocked\"}"
18 }
19 return "{\"result\":\"locked\"}"
20 }
21
22 func mgmtUnlockVaultAsync(paramsJSON string, respond func(string)) {
23 pw := helpers.JsonGetString(paramsJSON, "password")
24 if pw == "" {
25 respond("{\"error\":\"missing password\"}")
26 return
27 }
28 unlockVault(pw, func(ok bool) {
29 if ok {
30 respond("{\"result\":true}")
31 } else {
32 respond("{\"result\":false}")
33 }
34 })
35 }
36
37 func mgmtLockVault() string {
38 lockVault()
39 return "{\"result\":true}"
40 }
41
42 func mgmtCreateVaultAsync(paramsJSON string, respond func(string)) {
43 pw := helpers.JsonGetString(paramsJSON, "password")
44 if pw == "" {
45 respond("{\"error\":\"missing password\"}")
46 return
47 }
48 createVault(pw, func(ok bool) {
49 if ok {
50 respond("{\"result\":true}")
51 } else {
52 respond("{\"result\":false}")
53 }
54 })
55 }
56
57 func mgmtListIdentities() string {
58 if !vaultOpen {
59 return "{\"error\":\"vault locked\"}"
60 }
61 s := "["
62 for i, id := range identities {
63 if i > 0 {
64 s += ","
65 }
66 active := "false"
67 if i == activeIdx {
68 active = "true"
69 }
70 s += "{\"pubkey\":" + helpers.JsonString(id.Pubkey) +
71 ",\"name\":" + helpers.JsonString(id.Name) +
72 ",\"active\":" + active + "}"
73 }
74 return "{\"result\":" + s + "]}"
75 }
76
77 func mgmtSwitchIdentity(paramsJSON string) string {
78 pk := helpers.JsonGetString(paramsJSON, "pubkey")
79 for i, id := range identities {
80 if id.Pubkey == pk {
81 activeIdx = i
82 saveVault(nil)
83 return "{\"result\":true}"
84 }
85 }
86 return "{\"result\":false}"
87 }
88
89 // mgmtNsecLogin sets up an in-memory identity from an nsec without vault crypto.
90 // Used in dev mode where crypto.subtle may be unavailable (LAN over HTTP).
91 func mgmtNsecLogin(paramsJSON string) string {
92 nsec := helpers.JsonGetString(paramsJSON, "nsec")
93 if nsec == "" {
94 return "{\"error\":\"missing nsec\"}"
95 }
96 skBytes := helpers.DecodeNsec(nsec)
97 if skBytes == nil {
98 return "{\"error\":\"invalid nsec\"}"
99 }
100 pk, ok := schnorr.PubKeyFromSecKey(skBytes)
101 if !ok {
102 return "{\"error\":\"invalid key\"}"
103 }
104 pkHex := helpers.HexEncode(pk)
105 skHex := helpers.HexEncode(skBytes)
106
107 // Set up in-memory vault state without encryption.
108 vaultOpen = true
109 vaultExists = true
110 identities = []identity{{Pubkey: pkHex, Seckey: skHex}}
111 activeIdx = 0
112 return "{\"result\":true,\"pubkey\":" + helpers.JsonString(pkHex) + "}"
113 }
114
115 func mgmtAddIdentity(paramsJSON string) string {
116 if !vaultOpen {
117 return "{\"error\":\"vault locked\"}"
118 }
119 nsec := helpers.JsonGetString(paramsJSON, "nsec")
120 if nsec == "" {
121 return "{\"error\":\"missing nsec\"}"
122 }
123 skBytes := helpers.DecodeNsec(nsec)
124 if skBytes == nil {
125 return "{\"error\":\"invalid nsec\"}"
126 }
127 pk, ok := schnorr.PubKeyFromSecKey(skBytes)
128 if !ok {
129 return "{\"error\":\"invalid key\"}"
130 }
131 pkHex := helpers.HexEncode(pk)
132 skHex := helpers.HexEncode(skBytes)
133
134 // Check for duplicate.
135 for _, id := range identities {
136 if id.Pubkey == pkHex {
137 return "{\"error\":\"already exists\"}"
138 }
139 }
140
141 identities = append(identities, identity{Pubkey: pkHex, Seckey: skHex})
142 if activeIdx < 0 {
143 activeIdx = 0
144 }
145 saveVault(nil)
146 return "{\"result\":true,\"pubkey\":" + helpers.JsonString(pkHex) + "}"
147 }
148
149 func mgmtRemoveIdentity(paramsJSON string) string {
150 if !vaultOpen {
151 return "{\"error\":\"vault locked\"}"
152 }
153 pk := helpers.JsonGetString(paramsJSON, "pubkey")
154 for i, id := range identities {
155 if id.Pubkey == pk {
156 identities = append(identities[:i], identities[i+1:]...)
157 if activeIdx >= len(identities) {
158 activeIdx = len(identities) - 1
159 }
160 saveVault(nil)
161 return "{\"result\":true}"
162 }
163 }
164 return "{\"result\":false}"
165 }
166
167 func mgmtGetPermissions() string {
168 return getPermissionsJSON()
169 }
170
171 func mgmtSetPermission(paramsJSON string) string {
172 host := helpers.JsonGetString(paramsJSON, "host")
173 method := helpers.JsonGetString(paramsJSON, "method")
174 policy := helpers.JsonGetString(paramsJSON, "policy")
175 setPermission(host, method, policy)
176 return "{\"result\":true}"
177 }
178
179 func mgmtExportVaultAsync(paramsJSON string, respond func(string)) {
180 if !vaultOpen {
181 respond("{\"error\":\"vault locked\"}")
182 return
183 }
184 pw := helpers.JsonGetString(paramsJSON, "password")
185 if pw == "" {
186 respond("{\"error\":\"missing password\"}")
187 return
188 }
189
190 // Re-encrypt vault with export password: fresh salt, IV, Argon2id key.
191 salt := []byte{:32}
192 iv := []byte{:12}
193 subtle.RandomBytes(salt)
194 subtle.RandomBytes(iv)
195
196 passwordHash(pw, func(hash string) {
197 if hash == "" {
198 respond("{\"error\":\"hash failed\"}")
199 return
200 }
201 subtle.Argon2idDeriveKey(pw, salt, argon2T, argon2M, argon2P, argon2DKLen, func(key []byte) {
202 if len(key) == 0 {
203 respond("{\"error\":\"key derivation failed\"}")
204 return
205 }
206 // Encrypt identities with the export key.
207 encryptAllIdentities(key, iv, func(idJSON string) {
208 // Encrypt HD fields if present.
209 exportHDFields(key, iv, func(hdExtra string) {
210 s := "{\"version\":2" +
211 ",\"iv\":" + helpers.JsonString(helpers.Base64Encode(iv)) +
212 ",\"salt\":" + helpers.JsonString(helpers.Base64Encode(salt)) +
213 ",\"vaultHash\":" + helpers.JsonString(hash) +
214 ",\"identities\":" + idJSON +
215 ",\"permissions\":[]" +
216 ",\"relays\":[]" +
217 ",\"selectedIdentityId\":null" +
218 hdExtra + "}"
219 respond("{\"result\":" + s + "}")
220 })
221 })
222 })
223 })
224 }
225
226 func mgmtImportVaultAsync(paramsJSON string, respond func(string)) {
227 data := helpers.JsonGetString(paramsJSON, "data")
228 if data == "" {
229 respond("{\"error\":\"missing data\"}")
230 return
231 }
232 // Validate: must have version and vaultHash at minimum.
233 ver := helpers.JsonGetValue(data, "version")
234 vh := helpers.JsonGetString(data, "vaultHash")
235 if ver == "" || vh == "" {
236 respond("{\"error\":\"invalid vault format\"}")
237 return
238 }
239 // Store as-is; user must unlock with the original password.
240 vaultRawCache = data
241 vaultExists = true
242 vaultOpen = false
243 ext.StorageSet(vaultStorageKey, data)
244 respond("{\"result\":true}")
245 }
246
247 func mgmtPromptResponse(paramsJSON string) string {
248 // TODO: wire prompt response to pending sign request
249 return "{\"result\":true}"
250 }
251
252 // --- HD keychain management ---
253
254 func mgmtGenerateMnemonic() string {
255 m := generateMnemonic()
256 return "{\"result\":" + helpers.JsonString(m) + "}"
257 }
258
259 func mgmtValidateMnemonic(paramsJSON string) string {
260 m := helpers.JsonGetString(paramsJSON, "mnemonic")
261 if validateMnemonic(m) {
262 return "{\"result\":true}"
263 }
264 return "{\"result\":false}"
265 }
266
267 func mgmtCreateHDVaultAsync(paramsJSON string, respond func(string)) {
268 pw := helpers.JsonGetString(paramsJSON, "password")
269 name := helpers.JsonGetString(paramsJSON, "name")
270 if pw == "" {
271 respond("{\"error\":\"missing password\"}")
272 return
273 }
274 if name == "" {
275 name = "Identity 0"
276 }
277 hdCreateVault(pw, name, func(mnemonic string) {
278 if mnemonic == "" {
279 respond("{\"result\":false}")
280 } else {
281 respond("{\"result\":" + helpers.JsonString(mnemonic) + "}")
282 }
283 })
284 }
285
286 func mgmtRestoreHDVaultAsync(paramsJSON string, respond func(string)) {
287 pw := helpers.JsonGetString(paramsJSON, "password")
288 mnemonic := helpers.JsonGetString(paramsJSON, "mnemonic")
289 name := helpers.JsonGetString(paramsJSON, "name")
290 if pw == "" || mnemonic == "" {
291 respond("{\"error\":\"missing password or mnemonic\"}")
292 return
293 }
294 if name == "" {
295 name = "Identity 0"
296 }
297 hdRestoreVault(pw, mnemonic, name, func(ok bool) {
298 if ok {
299 respond("{\"result\":true}")
300 } else {
301 respond("{\"result\":false}")
302 }
303 })
304 }
305
306 func mgmtDeriveIdentityAsync(paramsJSON string, respond func(string)) {
307 if !vaultOpen {
308 respond("{\"error\":\"vault locked\"}")
309 return
310 }
311 if hdMnemonic == "" {
312 respond("{\"error\":\"not an HD vault\"}")
313 return
314 }
315 name := helpers.JsonGetString(paramsJSON, "name")
316 if name == "" {
317 name = "Identity " + itoa(hdNextAccount)
318 }
319 hdDeriveNext(name, func(pubkey string) {
320 if pubkey == "" {
321 respond("{\"result\":false}")
322 } else {
323 respond("{\"result\":" + helpers.JsonString(pubkey) + "}")
324 }
325 })
326 }
327
328 func mgmtGetMnemonic() string {
329 if !vaultOpen {
330 return "{\"error\":\"vault locked\"}"
331 }
332 if hdMnemonic == "" {
333 return "{\"result\":null}"
334 }
335 return "{\"result\":" + helpers.JsonString(hdMnemonic) + "}"
336 }
337
338 func mgmtProbeAccountAsync(paramsJSON string, respond func(string)) {
339 if !vaultOpen || hdMnemonic == "" {
340 respond("{\"result\":\"\"}")
341 return
342 }
343 idxStr := helpers.JsonGetValue(paramsJSON, "index")
344 idx := parseSimpleInt(idxStr)
345 probeHDAccount(idx, func(pk string) {
346 respond("{\"result\":" + helpers.JsonString(pk) + "}")
347 })
348 }
349
350 func mgmtIsHD() string {
351 if !vaultOpen {
352 return "{\"error\":\"vault locked\"}"
353 }
354 if hdMnemonic != "" {
355 return "{\"result\":true}"
356 }
357 return "{\"result\":false}"
358 }
359
360 func mgmtResetExtension() string {
361 lockVault()
362 vaultExists = false
363 vaultRawCache = ""
364 ext.StorageRemove(vaultStorageKey)
365 return "{\"result\":true}"
366 }
367