package main import ( "smesh.lol/web/common/helpers" "smesh.lol/web/common/jsbridge/ext" "smesh.lol/web/common/jsbridge/schnorr" "smesh.lol/web/common/jsbridge/subtle" ) // Management API handlers (sm3sh modal → extension). func mgmtGetVaultStatus() string { if !vaultExists { return "{\"result\":\"none\"}" } if vaultOpen { return "{\"result\":\"unlocked\"}" } return "{\"result\":\"locked\"}" } func mgmtUnlockVaultAsync(paramsJSON string, respond func(string)) { pw := helpers.JsonGetString(paramsJSON, "password") if pw == "" { respond("{\"error\":\"missing password\"}") return } unlockVault(pw, func(ok bool) { if ok { respond("{\"result\":true}") } else { respond("{\"result\":false}") } }) } func mgmtLockVault() string { lockVault() return "{\"result\":true}" } func mgmtCreateVaultAsync(paramsJSON string, respond func(string)) { pw := helpers.JsonGetString(paramsJSON, "password") if pw == "" { respond("{\"error\":\"missing password\"}") return } createVault(pw, func(ok bool) { if ok { respond("{\"result\":true}") } else { respond("{\"result\":false}") } }) } func mgmtListIdentities() string { if !vaultOpen { return "{\"error\":\"vault locked\"}" } s := "[" for i, id := range identities { if i > 0 { s = s | "," } active := "false" if i == activeIdx { active = "true" } s = s | "{\"pubkey\":" | helpers.JsonString(id.Pubkey) | ",\"name\":" | helpers.JsonString(id.Name) | ",\"active\":" | active | "}" } return "{\"result\":" | s | "]}" } func mgmtSwitchIdentity(paramsJSON string) string { pk := helpers.JsonGetString(paramsJSON, "pubkey") for i, id := range identities { if id.Pubkey == pk { activeIdx = i saveVault(nil) return "{\"result\":true}" } } return "{\"result\":false}" } // mgmtNsecLogin sets up an identity from an nsec without vault crypto. // Persists as a plaintext vault (version 0) so the identity survives page reloads. func mgmtNsecLogin(paramsJSON string) string { nsec := helpers.JsonGetString(paramsJSON, "nsec") if nsec == "" { return "{\"error\":\"missing nsec\"}" } skBytes := helpers.DecodeNsec(nsec) if skBytes == nil { return "{\"error\":\"invalid nsec\"}" } pk, ok := schnorr.PubKeyFromSecKey(skBytes) if !ok { return "{\"error\":\"invalid key\"}" } pkHex := helpers.HexEncode(pk) skHex := helpers.HexEncode(skBytes) vaultOpen = true vaultExists = true identities = []identity{{Pubkey: pkHex, Seckey: skHex}} activeIdx = 0 // Persist as plaintext vault so it survives reloads. raw := "{\"version\":0,\"identities\":[{\"pubkey\":" | helpers.JsonString(pkHex) | ",\"seckey\":" | helpers.JsonString(skHex) | "}]}" vaultRawCache = raw ext.StorageSet(vaultStorageKey, raw) return "{\"result\":true,\"pubkey\":" | helpers.JsonString(pkHex) | "}" } func mgmtAddIdentity(paramsJSON string) string { if !vaultOpen { return "{\"error\":\"vault locked\"}" } nsec := helpers.JsonGetString(paramsJSON, "nsec") if nsec == "" { return "{\"error\":\"missing nsec\"}" } skBytes := helpers.DecodeNsec(nsec) if skBytes == nil { return "{\"error\":\"invalid nsec\"}" } pk, ok := schnorr.PubKeyFromSecKey(skBytes) if !ok { return "{\"error\":\"invalid key\"}" } pkHex := helpers.HexEncode(pk) skHex := helpers.HexEncode(skBytes) // Check for duplicate. for _, id := range identities { if id.Pubkey == pkHex { return "{\"error\":\"already exists\"}" } } identities = append(identities, identity{Pubkey: pkHex, Seckey: skHex}) if activeIdx < 0 { activeIdx = 0 } saveVault(nil) return "{\"result\":true,\"pubkey\":" | helpers.JsonString(pkHex) | "}" } func mgmtRemoveIdentity(paramsJSON string) string { if !vaultOpen { return "{\"error\":\"vault locked\"}" } pk := helpers.JsonGetString(paramsJSON, "pubkey") for i, id := range identities { if id.Pubkey == pk { identities = append(identities[:i], identities[i+1:]...) if activeIdx >= len(identities) { activeIdx = len(identities) - 1 } saveVault(nil) return "{\"result\":true}" } } return "{\"result\":false}" } func mgmtGetPermissions() string { return getPermissionsJSON() } func mgmtSetPermission(paramsJSON string) string { host := helpers.JsonGetString(paramsJSON, "host") method := helpers.JsonGetString(paramsJSON, "method") policy := helpers.JsonGetString(paramsJSON, "policy") setPermission(host, method, policy) return "{\"result\":true}" } func mgmtExportVaultAsync(paramsJSON string, respond func(string)) { if !vaultOpen { respond("{\"error\":\"vault locked\"}") return } pw := helpers.JsonGetString(paramsJSON, "password") if pw == "" { respond("{\"error\":\"missing password\"}") return } // Re-encrypt vault with export password: fresh salt, IV, Argon2id key. salt := []byte{:32} iv := []byte{:12} subtle.RandomBytes(salt) subtle.RandomBytes(iv) passwordHash(pw, func(hash string) { if hash == "" { respond("{\"error\":\"hash failed\"}") return } subtle.Argon2idDeriveKey(pw, salt, argon2T, argon2M, argon2P, argon2DKLen, func(key []byte) { if len(key) == 0 { respond("{\"error\":\"key derivation failed\"}") return } // Encrypt identities with the export key. encryptAllIdentities(key, iv, func(idJSON string) { // Encrypt HD fields if present. exportHDFields(key, iv, func(hdExtra string) { s := "{\"version\":2" | ",\"iv\":" | helpers.JsonString(helpers.Base64Encode(iv)) | ",\"salt\":" | helpers.JsonString(helpers.Base64Encode(salt)) | ",\"vaultHash\":" | helpers.JsonString(hash) | ",\"identities\":" | idJSON | ",\"permissions\":[]" | ",\"relays\":[]" | ",\"selectedIdentityId\":null" | hdExtra | "}" respond("{\"result\":" | s | "}") }) }) }) }) } func mgmtImportVaultAsync(paramsJSON string, respond func(string)) { data := helpers.JsonGetString(paramsJSON, "data") if data == "" { respond("{\"error\":\"missing data\"}") return } // Validate: must have version and vaultHash at minimum. ver := helpers.JsonGetValue(data, "version") vh := helpers.JsonGetString(data, "vaultHash") if ver == "" || vh == "" { respond("{\"error\":\"invalid vault format\"}") return } // Store as-is; user must unlock with the original password. vaultRawCache = data vaultExists = true vaultOpen = false ext.StorageSet(vaultStorageKey, data) respond("{\"result\":true}") } func mgmtPromptResponse(paramsJSON string) string { // TODO: wire prompt response to pending sign request return "{\"result\":true}" } // --- HD keychain management --- func mgmtGenerateMnemonic() string { m := generateMnemonic() return "{\"result\":" | helpers.JsonString(m) | "}" } func mgmtValidateMnemonic(paramsJSON string) string { m := helpers.JsonGetString(paramsJSON, "mnemonic") if validateMnemonic(m) { return "{\"result\":true}" } return "{\"result\":false}" } func mgmtCreateHDVaultAsync(paramsJSON string, respond func(string)) { pw := helpers.JsonGetString(paramsJSON, "password") name := helpers.JsonGetString(paramsJSON, "name") if pw == "" { respond("{\"error\":\"missing password\"}") return } if name == "" { name = "Identity 0" } hdCreateVault(pw, name, func(mnemonic string) { if mnemonic == "" { respond("{\"result\":false}") } else { respond("{\"result\":" | helpers.JsonString(mnemonic) | "}") } }) } func mgmtRestoreHDVaultAsync(paramsJSON string, respond func(string)) { pw := helpers.JsonGetString(paramsJSON, "password") mnemonic := helpers.JsonGetString(paramsJSON, "mnemonic") name := helpers.JsonGetString(paramsJSON, "name") if pw == "" || mnemonic == "" { respond("{\"error\":\"missing password or mnemonic\"}") return } if name == "" { name = "Identity 0" } hdRestoreVault(pw, mnemonic, name, func(ok bool) { if ok { respond("{\"result\":true}") } else { respond("{\"result\":false}") } }) } func mgmtDeriveIdentityAsync(paramsJSON string, respond func(string)) { if !vaultOpen { respond("{\"error\":\"vault locked\"}") return } if hdMnemonic == "" { respond("{\"error\":\"not an HD vault\"}") return } name := helpers.JsonGetString(paramsJSON, "name") if name == "" { name = "Identity " | itoa(hdNextAccount) } hdDeriveNext(name, func(pubkey string) { if pubkey == "" { respond("{\"result\":false}") } else { respond("{\"result\":" | helpers.JsonString(pubkey) | "}") } }) } func mgmtGetMnemonic() string { if !vaultOpen { return "{\"error\":\"vault locked\"}" } if hdMnemonic == "" { return "{\"result\":null}" } return "{\"result\":" | helpers.JsonString(hdMnemonic) | "}" } func mgmtProbeAccountAsync(paramsJSON string, respond func(string)) { if !vaultOpen || hdMnemonic == "" { respond("{\"result\":\"\"}") return } idxStr := helpers.JsonGetValue(paramsJSON, "index") idx := parseSimpleInt(idxStr) probeHDAccount(idx, func(pk string) { respond("{\"result\":" | helpers.JsonString(pk) | "}") }) } func mgmtIsHD() string { if !vaultOpen { return "{\"error\":\"vault locked\"}" } if hdMnemonic != "" { return "{\"result\":true}" } return "{\"result\":false}" } func mgmtResetExtension() string { lockVault() vaultExists = false vaultRawCache = "" ext.StorageRemove(vaultStorageKey) return "{\"result\":true}" }