Skip to content
Snippets Groups Projects
Commit 39506054 authored by Richard T. Carback III's avatar Richard T. Carback III
Browse files

Merge branch 'project/HavenBeta' into projects/AccountSync

parents 07c3bbc7 e56c5f01
Branches
Tags
2 merge requests!114Notifications,!109Project/haven beta
Showing
with 175 additions and 607 deletions
......@@ -38,7 +38,6 @@ wasm-test:
- tags
script:
- export PATH=/root/go/bin:$PATH
- echo > utils/utils_js.s
- go mod vendor
- unset SSH_PRIVATE_KEY
- unset $(env | grep '=' | awk -F= '{print $1}' | grep -v PATH | grep -v GO | grep -v HOME)
......
......@@ -11,6 +11,7 @@ build:
GOOS=js GOARCH=wasm go build ./...
update_release:
GOFLAGS="" go get gitlab.com/elixxir/wasm-utils@release
GOFLAGS="" go get gitlab.com/xx_network/primitives@release
GOFLAGS="" go get gitlab.com/elixxir/primitives@release
GOFLAGS="" go get gitlab.com/xx_network/crypto@release
......@@ -18,11 +19,12 @@ update_release:
GOFLAGS="" go get -d gitlab.com/elixxir/client/v4@release
update_master:
GOFLAGS="" go get -d gitlab.com/elixxir/client@master
GOFLAGS="" go get gitlab.com/elixxir/crypto@master
GOFLAGS="" go get gitlab.com/elixxir/wasm-utils@master
GOFLAGS="" go get gitlab.com/xx_network/primitives@master
GOFLAGS="" go get gitlab.com/elixxir/primitives@master
GOFLAGS="" go get gitlab.com/xx_network/crypto@master
GOFLAGS="" go get gitlab.com/xx_network/primitives@master
GOFLAGS="" go get gitlab.com/elixxir/crypto@master
GOFLAGS="" go get -d gitlab.com/elixxir/client/v4@master
binary:
GOOS=js GOARCH=wasm go build -ldflags '-w -s' -trimpath -o xxdk.wasm main.go
......@@ -34,11 +36,16 @@ worker_binaries:
binaries: binary worker_binaries
wasmException = "vendor/gitlab.com/elixxir/wasm-utils/exception"
wasm_tests:
cp utils/utils_js.s utils/utils_js.s.bak
> utils/utils_js.s
cp $(wasmException)/throw_js.s $(wasmException)/throw_js.s.bak
cp $(wasmException)/throws.go $(wasmException)/throws.go.bak
> $(wasmException)/throw_js.s
cp $(wasmException)/throws.dev $(wasmException)/throws.go
-GOOS=js GOARCH=wasm go test -v ./...
mv utils/utils_js.s.bak utils/utils_js.s
mv $(wasmException)/throw_js.s.bak $(wasmException)/throw_js.s
mv $(wasmException)/throws.go.bak $(wasmException)/throws.go
go_tests:
go test ./... -v
......
......@@ -77,7 +77,7 @@ global.Go = class {
go: {
// ...
// func Throw(exception string, message string)
'gitlab.com/elixxir/xxdk-wasm/utils.throw': (sp) => {
'gitlab.com/elixxir/wasm-utils/utils.throw': (sp) => {
const exception = loadString(sp + 8)
const message = loadString(sp + 24)
throw globalThis[exception](message)
......
......@@ -15,8 +15,8 @@ import (
"gitlab.com/elixxir/client/v4/channels"
cft "gitlab.com/elixxir/client/v4/channelsFileTransfer"
"gitlab.com/elixxir/crypto/fileTransfer"
"gitlab.com/elixxir/wasm-utils/utils"
"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
"gitlab.com/elixxir/xxdk-wasm/utils"
"strings"
"time"
)
......
......@@ -27,8 +27,9 @@ import (
cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
cryptoChannel "gitlab.com/elixxir/crypto/channel"
"gitlab.com/elixxir/crypto/message"
"gitlab.com/elixxir/wasm-utils/utils"
"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
"gitlab.com/elixxir/xxdk-wasm/utils"
wChannels "gitlab.com/elixxir/xxdk-wasm/indexedDb/worker/channels"
"gitlab.com/xx_network/primitives/id"
)
......
......@@ -30,8 +30,8 @@ import (
cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
cryptoChannel "gitlab.com/elixxir/crypto/channel"
"gitlab.com/elixxir/crypto/message"
"gitlab.com/elixxir/wasm-utils/storage"
"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
"gitlab.com/elixxir/xxdk-wasm/storage"
"gitlab.com/xx_network/crypto/csprng"
"gitlab.com/xx_network/primitives/id"
"gitlab.com/xx_network/primitives/netTime"
......
......@@ -23,7 +23,7 @@ import (
// currentVersion is the current version of the IndexedDb runtime. Used for
// migration purposes.
const currentVersion uint = 2
const currentVersion uint = 1
// NewWASMEventModel returns a [channels.EventModel] backed by a wasmModel.
// The name should be a base64 encoding of the users public key. Returns the
......@@ -57,14 +57,6 @@ func newWASMModel(databaseName string, encryption cryptoChannel.Cipher,
oldVersion = 1
}
if oldVersion == 1 && newVersion >= 2 {
err := v2Upgrade(db)
if err != nil {
return err
}
oldVersion = 2
}
// if oldVersion == 1 && newVersion >= 2 { v2Upgrade(), oldVersion = 2 }
return nil
})
......@@ -143,15 +135,8 @@ func v1Upgrade(db *idb.Database) error {
return err
}
return nil
}
// v1Upgrade performs the v1 -> v2 database upgrade.
//
// This can never be changed without permanently breaking backwards
// compatibility.
func v2Upgrade(db *idb.Database) error {
_, err := db.CreateObjectStore(fileStoreName, idb.ObjectStoreOptions{
// Build File ObjectStore
_, err = db.CreateObjectStore(fileStoreName, idb.ObjectStoreOptions{
KeyPath: js.ValueOf(pkeyName),
AutoIncrement: false,
})
......
......@@ -13,6 +13,7 @@ import (
"bytes"
"crypto/ed25519"
"encoding/json"
"gitlab.com/xx_network/primitives/netTime"
"strings"
"syscall/js"
"time"
......@@ -25,8 +26,8 @@ import (
"gitlab.com/elixxir/client/v4/dm"
cryptoChannel "gitlab.com/elixxir/crypto/channel"
"gitlab.com/elixxir/crypto/message"
"gitlab.com/elixxir/wasm-utils/utils"
"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
"gitlab.com/elixxir/xxdk-wasm/utils"
"gitlab.com/xx_network/primitives/id"
)
......@@ -42,7 +43,7 @@ type wasmModel struct {
// upsertConversation is used for joining or updating a Conversation.
func (w *wasmModel) upsertConversation(nickname string,
pubKey ed25519.PublicKey, partnerToken uint32, codeset uint8,
blocked bool) error {
blockedTimestamp *time.Time) error {
parentErr := errors.New("[DM indexedDB] failed to upsertConversation")
// Build object
......@@ -51,7 +52,7 @@ func (w *wasmModel) upsertConversation(nickname string,
Nickname: nickname,
Token: partnerToken,
CodesetVersion: codeset,
Blocked: blocked,
BlockedTimestamp: blockedTimestamp,
}
// Convert to jsObject
......@@ -235,7 +236,7 @@ func (w *wasmModel) receiveWrapper(messageID message.ID, parentID *message.ID, n
Nickname: nickname,
Token: partnerToken,
CodesetVersion: codeset,
Blocked: false,
BlockedTimestamp: nil,
}
}
} else {
......@@ -268,7 +269,7 @@ func (w *wasmModel) receiveWrapper(messageID message.ID, parentID *message.ID, n
conversationUpdated := convoToUpdate != nil
if conversationUpdated {
err = w.upsertConversation(convoToUpdate.Nickname, convoToUpdate.Pubkey,
convoToUpdate.Token, convoToUpdate.CodesetVersion, convoToUpdate.Blocked)
convoToUpdate.Token, convoToUpdate.CodesetVersion, convoToUpdate.BlockedTimestamp)
if err != nil {
return 0, err
}
......@@ -349,14 +350,20 @@ func (w *wasmModel) UnblockSender(senderPubKey ed25519.PublicKey) {
// setBlocked is a helper for blocking/unblocking a given Conversation.
func (w *wasmModel) setBlocked(senderPubKey ed25519.PublicKey, isBlocked bool) error {
// Get current Conversation and set blocked
// Get current Conversation and set blocked accordingly
resultConvo, err := w.getConversation(senderPubKey)
if err != nil {
return err
}
var timeBlocked *time.Time
if isBlocked {
blockUser := netTime.Now()
timeBlocked = &blockUser
}
return w.upsertConversation(resultConvo.Nickname, resultConvo.Pubkey,
resultConvo.Token, resultConvo.CodesetVersion, isBlocked)
resultConvo.Token, resultConvo.CodesetVersion, timeBlocked)
}
// GetConversation returns the conversation held by the model (receiver).
......@@ -373,7 +380,7 @@ func (w *wasmModel) GetConversation(senderPubKey ed25519.PublicKey) *dm.ModelCon
Nickname: resultConvo.Nickname,
Token: resultConvo.Token,
CodesetVersion: resultConvo.CodesetVersion,
Blocked: resultConvo.Blocked,
BlockedTimestamp: resultConvo.BlockedTimestamp,
}
}
......@@ -415,7 +422,7 @@ func (w *wasmModel) GetConversations() []dm.ModelConversation {
Nickname: resultConvo.Nickname,
Token: resultConvo.Token,
CodesetVersion: resultConvo.CodesetVersion,
Blocked: resultConvo.Blocked,
BlockedTimestamp: resultConvo.BlockedTimestamp,
}
}
return conversations
......
......@@ -17,8 +17,8 @@ import (
"gitlab.com/elixxir/client/v4/cmix/rounds"
"gitlab.com/elixxir/client/v4/dm"
"gitlab.com/elixxir/crypto/message"
"gitlab.com/elixxir/wasm-utils/utils"
"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
"gitlab.com/elixxir/xxdk-wasm/utils"
"gitlab.com/xx_network/primitives/id"
"os"
"syscall/js"
......@@ -102,7 +102,7 @@ func TestImpl_GetConversations(t *testing.T) {
testBytes := []byte(fmt.Sprintf("%d", i))
testPubKey := ed25519.PublicKey(testBytes)
err = m.upsertConversation("test", testPubKey,
uint32(i), uint8(i), false)
uint32(i), uint8(i), nil)
if err != nil {
t.Fatal(err.Error())
}
......@@ -133,28 +133,28 @@ func TestWasmModel_BlockSender(t *testing.T) {
// Insert a test convo
testPubKey := ed25519.PublicKey{}
err = m.upsertConversation("test", testPubKey, 0, 0, false)
err = m.upsertConversation("test", testPubKey, 0, 0, nil)
if err != nil {
t.Fatal(err.Error())
}
// Default to unblocked
result := m.GetConversation(testPubKey)
if result.Blocked {
if result.BlockedTimestamp != nil {
t.Fatal("Expected blocked to be false")
}
// Now toggle blocked
m.BlockSender(testPubKey)
result = m.GetConversation(testPubKey)
if !result.Blocked {
if result.BlockedTimestamp == nil {
t.Fatal("Expected blocked to be true")
}
// Now toggle blocked again
m.UnblockSender(testPubKey)
result = m.GetConversation(testPubKey)
if result.Blocked {
if result.BlockedTimestamp != nil {
t.Fatal("Expected blocked to be false")
}
}
......@@ -59,5 +59,5 @@ type Conversation struct {
Nickname string `json:"nickname"`
Token uint32 `json:"token"`
CodesetVersion uint8 `json:"codeset_version"`
Blocked bool `json:"blocked"`
BlockedTimestamp *time.Time `json:"blocked_timestamp"`
}
......@@ -18,7 +18,7 @@ import (
"github.com/hack-pad/go-indexeddb/idb"
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/xxdk-wasm/utils"
"gitlab.com/elixxir/wasm-utils/utils"
"syscall/js"
"time"
)
......
......@@ -15,7 +15,7 @@ import (
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/xxdk-wasm/utils"
"gitlab.com/elixxir/wasm-utils/utils"
"gitlab.com/elixxir/xxdk-wasm/worker"
)
......
......@@ -18,9 +18,9 @@ import (
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/wasm-utils/utils"
"gitlab.com/elixxir/xxdk-wasm/logging"
"gitlab.com/elixxir/xxdk-wasm/storage"
"gitlab.com/elixxir/xxdk-wasm/utils"
"gitlab.com/elixxir/xxdk-wasm/wasm"
)
......
......@@ -12,6 +12,8 @@ package storage
import (
"github.com/pkg/errors"
"os"
"gitlab.com/elixxir/wasm-utils/storage"
)
// Key to store if the database is encrypted or not
......@@ -22,12 +24,15 @@ const databaseEncryptionToggleKey = "xxdkWasmDatabaseEncryptionToggle/"
func StoreIndexedDbEncryptionStatus(
databaseName string, encryptionStatus bool) (
loadedEncryptionStatus bool, err error) {
data, err := GetLocalStorage().GetItem(
databaseEncryptionToggleKey + databaseName)
ls := storage.GetLocalStorage()
data, err := ls.Get(databaseEncryptionToggleKey + databaseName)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
GetLocalStorage().SetItem(
databaseEncryptionToggleKey+databaseName, []byte{1})
keyName := databaseEncryptionToggleKey + databaseName
if err = ls.Set(keyName, []byte{1}); err != nil {
return false,
errors.Wrapf(err, "localStorage: failed to set %q", keyName)
}
return encryptionStatus, nil
} else {
return false, err
......
......@@ -11,8 +11,11 @@ package storage
import (
"encoding/json"
"github.com/pkg/errors"
"os"
"github.com/pkg/errors"
"gitlab.com/elixxir/wasm-utils/storage"
)
const indexedDbListKey = "xxDkWasmIndexedDbList"
......@@ -20,7 +23,7 @@ const indexedDbListKey = "xxDkWasmIndexedDbList"
// GetIndexedDbList returns the list of stored indexedDb databases.
func GetIndexedDbList() (map[string]struct{}, error) {
list := make(map[string]struct{})
listBytes, err := GetLocalStorage().GetItem(indexedDbListKey)
listBytes, err := storage.GetLocalStorage().Get(indexedDbListKey)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err
} else if err == nil {
......@@ -47,7 +50,11 @@ func StoreIndexedDb(databaseName string) error {
return err
}
GetLocalStorage().SetItem(indexedDbListKey, listBytes)
err = storage.GetLocalStorage().Set(indexedDbListKey, listBytes)
if err != nil {
return errors.Wrapf(err,
"localStorage: failed to set %q", indexedDbListKey)
}
return nil
}
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 xx foundation //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file. //
////////////////////////////////////////////////////////////////////////////////
//go:build js && wasm
package storage
import (
"encoding/base64"
"encoding/json"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/xxdk-wasm/utils"
"os"
"strings"
"syscall/js"
)
// localStorageWasmPrefix is prefixed to every keyName saved to local storage by
// LocalStorage. It allows the identifications and deletion of keys only created
// by this WASM binary while ignoring keys made by other scripts on the same
// page.
const localStorageWasmPrefix = "xxdkWasmStorage/"
// LocalStorage contains the js.Value representation of localStorage.
type LocalStorage struct {
// The Javascript value containing the localStorage object
v js.Value
// The prefix appended to each key name. This is so that all keys created by
// this structure can be deleted without affecting other keys in local
// storage.
prefix string
}
// jsStorage is the global that stores Javascript as window.localStorage.
//
// - Specification:
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-localstorage-dev
// - Documentation:
// https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
var jsStorage = newLocalStorage(localStorageWasmPrefix)
// newLocalStorage creates a new LocalStorage object with the specified prefix.
func newLocalStorage(prefix string) *LocalStorage {
return &LocalStorage{
v: js.Global().Get("localStorage"),
prefix: prefix,
}
}
// GetLocalStorage returns Javascript's local storage.
func GetLocalStorage() *LocalStorage {
return jsStorage
}
// GetItem returns a key's value from the local storage given its name. Returns
// os.ErrNotExist if the key does not exist. Underneath, it calls
// localStorage.GetItem().
//
// - Specification:
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-getitem-dev
// - Documentation:
// https://developer.mozilla.org/en-US/docs/Web/API/Storage/getItem
func (ls *LocalStorage) GetItem(keyName string) ([]byte, error) {
keyValue := ls.getItem(ls.prefix + keyName)
if keyValue.IsNull() {
return nil, os.ErrNotExist
}
decodedKeyValue, err := base64.StdEncoding.DecodeString(keyValue.String())
if err != nil {
return nil, err
}
return decodedKeyValue, nil
}
// SetItem adds a key's value to local storage given its name. Underneath, it
// calls localStorage.SetItem().
//
// - Specification:
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-setitem-dev
// - Documentation:
// https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem
func (ls *LocalStorage) SetItem(keyName string, keyValue []byte) {
encodedKeyValue := base64.StdEncoding.EncodeToString(keyValue)
ls.setItem(ls.prefix+keyName, encodedKeyValue)
}
// RemoveItem removes a key's value from local storage given its name. If there
// is no item with the given key, this function does nothing. Underneath, it
// calls localStorage.RemoveItem().
//
// - Specification:
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-removeitem-dev
// - Documentation:
// https://developer.mozilla.org/en-US/docs/Web/API/Storage/removeItem
func (ls *LocalStorage) RemoveItem(keyName string) {
ls.removeItem(ls.prefix + keyName)
}
// Clear clears all the keys in storage. Underneath, it calls
// localStorage.clear().
//
// - Specification:
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-clear-dev
// - Documentation:
// https://developer.mozilla.org/en-US/docs/Web/API/Storage/clear
func (ls *LocalStorage) Clear() {
ls.clear()
}
// ClearPrefix clears all keys with the given prefix. Returns the number of
// keys cleared.
func (ls *LocalStorage) ClearPrefix(prefix string) int {
// Get a copy of all key names at once
keys := ls.keys()
// Loop through each key
var n int
for i := 0; i < keys.Length(); i++ {
if v := keys.Index(i); !v.IsNull() {
keyName := strings.TrimPrefix(v.String(), ls.prefix)
if strings.HasPrefix(keyName, prefix) {
ls.removeItem(v.String())
n++
}
}
}
return n
}
// ClearWASM clears all the keys in storage created by WASM. Returns the number
// of keys cleared.
func (ls *LocalStorage) ClearWASM() int {
// Get a copy of all key names at once
keys := ls.keys()
// Loop through each key
var n int
for i := 0; i < keys.Length(); i++ {
if v := keys.Index(i); !v.IsNull() {
keyName := v.String()
if strings.HasPrefix(keyName, ls.prefix) {
ls.RemoveItem(strings.TrimPrefix(keyName, ls.prefix))
n++
}
}
}
return n
}
// Key returns the name of the nth key in localStorage. Return os.ErrNotExist if
// the key does not exist. The order of keys is not defined. If there is no item
// with the given key, this function does nothing. Underneath, it calls
// localStorage.key().
//
// - Specification:
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-key-dev
// - Documentation:
// https://developer.mozilla.org/en-US/docs/Web/API/Storage/key
func (ls *LocalStorage) Key(n int) (string, error) {
keyName := ls.key(n)
if keyName.IsNull() {
return "", os.ErrNotExist
}
return strings.TrimPrefix(keyName.String(), ls.prefix), nil
}
// Keys returns a list of all key names in local storage.
func (ls *LocalStorage) Keys() []string {
keyNamesJson := utils.JSON.Call("stringify", ls.keys())
var keyNames []string
err := json.Unmarshal([]byte(keyNamesJson.String()), &keyNames)
if err != nil {
jww.FATAL.Panicf(
"Failed to JSON unmarshal localStorage key name list: %+v", err)
}
return keyNames
}
// Length returns the number of keys in localStorage. Underneath, it accesses
// the property localStorage.length.
//
// - Specification:
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-key-dev
// - Documentation:
// https://developer.mozilla.org/en-US/docs/Web/API/Storage/length
func (ls *LocalStorage) Length() int {
return ls.length().Int()
}
// Wrappers for Javascript Storage methods and properties.
func (ls *LocalStorage) getItem(keyName string) js.Value { return ls.v.Call("getItem", keyName) }
func (ls *LocalStorage) setItem(keyName, keyValue string) { ls.v.Call("setItem", keyName, keyValue) }
func (ls *LocalStorage) removeItem(keyName string) { ls.v.Call("removeItem", keyName) }
func (ls *LocalStorage) clear() { ls.v.Call("clear") }
func (ls *LocalStorage) key(n int) js.Value { return ls.v.Call("key", n) }
func (ls *LocalStorage) length() js.Value { return ls.v.Get("length") }
func (ls *LocalStorage) keys() js.Value { return utils.Object.Call("keys", ls.v) }
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 xx foundation //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file. //
////////////////////////////////////////////////////////////////////////////////
//go:build js && wasm
package storage
import (
"bytes"
"github.com/pkg/errors"
"math/rand"
"os"
"strconv"
"testing"
)
// Tests that a value set with LocalStorage.SetItem and retrieved with
// LocalStorage.GetItem matches the original.
func TestLocalStorage_GetItem_SetItem(t *testing.T) {
values := map[string][]byte{
"key1": []byte("key value"),
"key2": {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
"key3": {0, 49, 0, 0, 0, 38, 249, 93, 242, 189, 222, 32, 138, 248, 121,
151, 42, 108, 82, 199, 163, 61, 4, 200, 140, 231, 225, 20, 35, 243,
253, 161, 61, 2, 227, 208, 173, 183, 33, 66, 236, 107, 105, 119, 26,
42, 44, 60, 109, 172, 38, 47, 220, 17, 129, 4, 234, 241, 141, 81,
84, 185, 32, 120, 115, 151, 128, 196, 143, 117, 222, 78, 44, 115,
109, 20, 249, 46, 158, 139, 231, 157, 54, 219, 141, 252},
}
for keyName, keyValue := range values {
jsStorage.SetItem(keyName, keyValue)
loadedValue, err := jsStorage.GetItem(keyName)
if err != nil {
t.Errorf("Failed to load %q: %+v", keyName, err)
}
if !bytes.Equal(keyValue, loadedValue) {
t.Errorf("Loaded value does not match original for %q"+
"\nexpected: %q\nreceived: %q", keyName, keyValue, loadedValue)
}
}
}
// Tests that LocalStorage.GetItem returns the error os.ErrNotExist when the key
// does not exist in storage.
func TestLocalStorage_GetItem_NotExistError(t *testing.T) {
_, err := jsStorage.GetItem("someKey")
if err == nil || !errors.Is(err, os.ErrNotExist) {
t.Errorf("Incorrect error for non existant key."+
"\nexpected: %v\nreceived: %v", os.ErrNotExist, err)
}
}
// Tests that LocalStorage.RemoveItem deletes a key from store and that it
// cannot be retrieved.
func TestLocalStorage_RemoveItem(t *testing.T) {
keyName := "key"
jsStorage.SetItem(keyName, []byte("value"))
jsStorage.RemoveItem(keyName)
_, err := jsStorage.GetItem(keyName)
if err == nil || !errors.Is(err, os.ErrNotExist) {
t.Errorf("Failed to remove %q: %+v", keyName, err)
}
}
// Tests that LocalStorage.Clear deletes all keys from storage.
func TestLocalStorage_Clear(t *testing.T) {
for i := 0; i < 10; i++ {
jsStorage.SetItem(strconv.Itoa(i), []byte(strconv.Itoa(i)))
}
jsStorage.Clear()
l := jsStorage.Length()
if l > 0 {
t.Errorf("Clear did not delete all keys. Found %d keys.", l)
}
}
// Tests that LocalStorage.ClearPrefix deletes only the keys with the given
// prefix.
func TestLocalStorage_ClearPrefix(t *testing.T) {
s := newLocalStorage("")
s.clear()
prng := rand.New(rand.NewSource(11))
const numKeys = 10
var yesPrefix, noPrefix []string
prefix := "keyNamePrefix/"
for i := 0; i < numKeys; i++ {
keyName := "keyNum" + strconv.Itoa(i)
if prng.Intn(2) == 0 {
keyName = prefix + keyName
yesPrefix = append(yesPrefix, keyName)
} else {
noPrefix = append(noPrefix, keyName)
}
s.SetItem(keyName, []byte(strconv.Itoa(i)))
}
n := s.ClearPrefix(prefix)
if n != numKeys/2 {
t.Errorf("Incorrect number of keys.\nexpected: %d\nreceived: %d",
numKeys/2, n)
}
for _, keyName := range noPrefix {
if _, err := s.GetItem(keyName); err != nil {
t.Errorf("Could not get keyName %q: %+v", keyName, err)
}
}
for _, keyName := range yesPrefix {
keyValue, err := s.GetItem(keyName)
if err == nil || !errors.Is(err, os.ErrNotExist) {
t.Errorf("Found keyName %q: %q", keyName, keyValue)
}
}
}
// Tests that LocalStorage.ClearWASM deletes all the WASM keys from storage and
// does not remove any others
func TestLocalStorage_ClearWASM(t *testing.T) {
jsStorage.clear()
prng := rand.New(rand.NewSource(11))
const numKeys = 10
var yesPrefix, noPrefix []string
for i := 0; i < numKeys; i++ {
keyName := "keyNum" + strconv.Itoa(i)
if prng.Intn(2) == 0 {
yesPrefix = append(yesPrefix, keyName)
jsStorage.SetItem(keyName, []byte(strconv.Itoa(i)))
} else {
noPrefix = append(noPrefix, keyName)
jsStorage.setItem(keyName, strconv.Itoa(i))
}
}
n := jsStorage.ClearWASM()
if n != numKeys/2 {
t.Errorf("Incorrect number of keys.\nexpected: %d\nreceived: %d",
numKeys/2, n)
}
for _, keyName := range noPrefix {
if v := jsStorage.getItem(keyName); v.IsNull() {
t.Errorf("Could not get keyName %q.", keyName)
}
}
for _, keyName := range yesPrefix {
keyValue, err := jsStorage.GetItem(keyName)
if err == nil || !errors.Is(err, os.ErrNotExist) {
t.Errorf("Found keyName %q: %q", keyName, keyValue)
}
}
}
// Tests that LocalStorage.Key return all added keys when looping through all
// indexes.
func TestLocalStorage_Key(t *testing.T) {
jsStorage.clear()
values := map[string][]byte{
"key1": []byte("key value"),
"key2": {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
"key3": {0, 49, 0, 0, 0, 38, 249, 93},
}
for keyName, keyValue := range values {
jsStorage.SetItem(keyName, keyValue)
}
numKeys := len(values)
for i := 0; i < numKeys; i++ {
keyName, err := jsStorage.Key(i)
if err != nil {
t.Errorf("No key found for index %d: %+v", i, err)
}
if _, exists := values[keyName]; !exists {
t.Errorf("No key with name %q added to storage.", keyName)
}
delete(values, keyName)
}
if len(values) != 0 {
t.Errorf("%d keys not read from storage: %q", len(values), values)
}
}
// Tests that LocalStorage.Key returns the error os.ErrNotExist when the index
// is greater than or equal to the number of keys.
func TestLocalStorage_Key_NotExistError(t *testing.T) {
jsStorage.clear()
jsStorage.SetItem("key", []byte("value"))
_, err := jsStorage.Key(1)
if err == nil || !errors.Is(err, os.ErrNotExist) {
t.Errorf("Incorrect error for non existant key index."+
"\nexpected: %v\nreceived: %v", os.ErrNotExist, err)
}
_, err = jsStorage.Key(2)
if err == nil || !errors.Is(err, os.ErrNotExist) {
t.Errorf("Incorrect error for non existant key index."+
"\nexpected: %v\nreceived: %v", os.ErrNotExist, err)
}
}
// Tests that LocalStorage.Length returns the correct Length when adding and
// removing various keys.
func TestLocalStorage_Length(t *testing.T) {
jsStorage.clear()
values := map[string][]byte{
"key1": []byte("key value"),
"key2": {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
"key3": {0, 49, 0, 0, 0, 38, 249, 93},
}
i := 0
for keyName, keyValue := range values {
jsStorage.SetItem(keyName, keyValue)
i++
if jsStorage.Length() != i {
t.Errorf("Incorrect length.\nexpected: %d\nreceived: %d",
i, jsStorage.Length())
}
}
i = len(values)
for keyName := range values {
jsStorage.RemoveItem(keyName)
i--
if jsStorage.Length() != i {
t.Errorf("Incorrect length.\nexpected: %d\nreceived: %d",
i, jsStorage.Length())
}
}
}
// Tests that LocalStorage.Keys return a list that contains all the added keys.
func TestLocalStorage_Keys(t *testing.T) {
s := newLocalStorage("")
s.clear()
values := map[string][]byte{
"key1": []byte("key value"),
"key2": {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
"key3": {0, 49, 0, 0, 0, 38, 249, 93},
}
for keyName, keyValue := range values {
s.SetItem(keyName, keyValue)
}
keys := s.Keys()
for i, keyName := range keys {
if _, exists := values[keyName]; !exists {
t.Errorf("Key %q does not exist (%d).", keyName, i)
}
}
}
......@@ -12,16 +12,21 @@ package storage
import (
"crypto/cipher"
"encoding/json"
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/xxdk-wasm/utils"
"gitlab.com/xx_network/crypto/csprng"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/chacha20poly1305"
"io"
"os"
"syscall/js"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/chacha20poly1305"
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/wasm-utils/exception"
"gitlab.com/elixxir/wasm-utils/storage"
"gitlab.com/elixxir/wasm-utils/utils"
"gitlab.com/xx_network/crypto/csprng"
)
// Data lengths.
......@@ -91,7 +96,7 @@ const (
func GetOrInitPassword(_ js.Value, args []js.Value) any {
internalPassword, err := getOrInit(args[0].String())
if err != nil {
utils.Throw(utils.TypeError, err)
exception.ThrowTrace(err)
return nil
}
......@@ -109,7 +114,7 @@ func GetOrInitPassword(_ js.Value, args []js.Value) any {
func ChangeExternalPassword(_ js.Value, args []js.Value) any {
err := changeExternalPassword(args[0].String(), args[1].String())
if err != nil {
utils.Throw(utils.TypeError, err)
exception.ThrowTrace(err)
return nil
}
......@@ -130,7 +135,7 @@ func VerifyPassword(_ js.Value, args []js.Value) any {
// getOrInit is the private function for GetOrInitPassword that is used for
// testing.
func getOrInit(externalPassword string) ([]byte, error) {
localStorage := GetLocalStorage()
localStorage := storage.GetLocalStorage()
internalPassword, err := getInternalPassword(externalPassword, localStorage)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
......@@ -148,7 +153,7 @@ func getOrInit(externalPassword string) ([]byte, error) {
// changeExternalPassword is the private function for ChangeExternalPassword
// that is used for testing.
func changeExternalPassword(oldExternalPassword, newExternalPassword string) error {
localStorage := GetLocalStorage()
localStorage := storage.GetLocalStorage()
internalPassword, err := getInternalPassword(
oldExternalPassword, localStorage)
if err != nil {
......@@ -159,13 +164,17 @@ func changeExternalPassword(oldExternalPassword, newExternalPassword string) err
if err != nil {
return err
}
localStorage.SetItem(saltKey, salt)
if err = localStorage.Set(saltKey, salt); err != nil {
return errors.Wrapf(err, "localStorage: failed to set %q", saltKey)
}
key := deriveKey(newExternalPassword, salt, defaultParams())
encryptedInternalPassword := encryptPassword(
internalPassword, key, csprng.NewSystemRNG())
localStorage.SetItem(passwordKey, encryptedInternalPassword)
if err = localStorage.Set(passwordKey, encryptedInternalPassword); err != nil {
return errors.Wrapf(err, "localStorage: failed to set %q", passwordKey)
}
return nil
}
......@@ -173,14 +182,14 @@ func changeExternalPassword(oldExternalPassword, newExternalPassword string) err
// verifyPassword is the private function for VerifyPassword that is used for
// testing.
func verifyPassword(externalPassword string) bool {
_, err := getInternalPassword(externalPassword, GetLocalStorage())
_, err := getInternalPassword(externalPassword, storage.GetLocalStorage())
return err == nil
}
// initInternalPassword generates a new internal password, stores an encrypted
// version in local storage, and returns it.
func initInternalPassword(externalPassword string,
localStorage *LocalStorage, csprng io.Reader,
localStorage *storage.LocalStorage, csprng io.Reader,
params argonParams) ([]byte, error) {
internalPassword := make([]byte, internalPasswordLen)
......@@ -198,19 +207,28 @@ func initInternalPassword(externalPassword string,
if err != nil {
return nil, err
}
localStorage.SetItem(saltKey, salt)
if err = localStorage.Set(saltKey, salt); err != nil {
return nil,
errors.Wrapf(err, "localStorage: failed to set %q", saltKey)
}
// Store argon2 parameters
paramsData, err := json.Marshal(params)
if err != nil {
return nil, err
}
localStorage.SetItem(argonParamsKey, paramsData)
if err = localStorage.Set(argonParamsKey, paramsData); err != nil {
return nil,
errors.Wrapf(err, "localStorage: failed to set %q", argonParamsKey)
}
key := deriveKey(externalPassword, salt, params)
encryptedInternalPassword := encryptPassword(internalPassword, key, csprng)
localStorage.SetItem(passwordKey, encryptedInternalPassword)
if err = localStorage.Set(passwordKey, encryptedInternalPassword); err != nil {
return nil,
errors.Wrapf(err, "localStorage: failed to set %q", passwordKey)
}
return internalPassword, nil
}
......@@ -218,18 +236,18 @@ func initInternalPassword(externalPassword string,
// getInternalPassword retrieves the internal password from local storage,
// decrypts it, and returns it.
func getInternalPassword(
externalPassword string, localStorage *LocalStorage) ([]byte, error) {
encryptedInternalPassword, err := localStorage.GetItem(passwordKey)
externalPassword string, localStorage *storage.LocalStorage) ([]byte, error) {
encryptedInternalPassword, err := localStorage.Get(passwordKey)
if err != nil {
return nil, errors.WithMessage(err, getPasswordStorageErr)
}
salt, err := localStorage.GetItem(saltKey)
salt, err := localStorage.Get(saltKey)
if err != nil {
return nil, errors.WithMessage(err, getSaltStorageErr)
}
paramsData, err := localStorage.GetItem(argonParamsKey)
paramsData, err := localStorage.Get(argonParamsKey)
if err != nil {
return nil, errors.WithMessage(err, getParamsStorageErr)
}
......
......@@ -14,9 +14,11 @@ import (
"crypto/rand"
"encoding/base64"
"fmt"
"gitlab.com/xx_network/crypto/csprng"
"strings"
"testing"
"gitlab.com/elixxir/wasm-utils/storage"
"gitlab.com/xx_network/crypto/csprng"
)
// Tests that running getOrInit twice returns the same internal password both
......@@ -77,7 +79,7 @@ func Test_changeExternalPassword(t *testing.T) {
// Tests that verifyPassword returns true for a valid password and false for an
// invalid password
func Test_verifyPassword(t *testing.T) {
GetLocalStorage().Clear()
storage.GetLocalStorage().Clear()
externalPassword := "myPassword"
if _, err := getOrInit(externalPassword); err != nil {
......@@ -97,7 +99,7 @@ func Test_verifyPassword(t *testing.T) {
// the encrypted one saved to local storage.
func Test_initInternalPassword(t *testing.T) {
externalPassword := "myPassword"
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
rng := csprng.NewSystemRNG()
internalPassword, err := initInternalPassword(
......@@ -107,14 +109,14 @@ func Test_initInternalPassword(t *testing.T) {
}
// Attempt to retrieve encrypted internal password from storage
encryptedInternalPassword, err := ls.GetItem(passwordKey)
encryptedInternalPassword, err := ls.Get(passwordKey)
if err != nil {
t.Errorf(
"Failed to load encrypted internal password from storage: %+v", err)
}
// Attempt to retrieve salt from storage
salt, err := ls.GetItem(saltKey)
salt, err := ls.Get(saltKey)
if err != nil {
t.Errorf("Failed to load salt from storage: %+v", err)
}
......@@ -138,7 +140,7 @@ func Test_initInternalPassword(t *testing.T) {
// error when read.
func Test_initInternalPassword_CsprngReadError(t *testing.T) {
externalPassword := "myPassword"
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
b := bytes.NewBuffer([]byte{})
expectedErr := strings.Split(readInternalPasswordErr, "%")[0]
......@@ -154,7 +156,7 @@ func Test_initInternalPassword_CsprngReadError(t *testing.T) {
// return enough bytes.
func Test_initInternalPassword_CsprngReadNumBytesError(t *testing.T) {
externalPassword := "myPassword"
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
b := bytes.NewBuffer(make([]byte, internalPasswordLen/2))
expectedErr := fmt.Sprintf(
......@@ -171,7 +173,7 @@ func Test_initInternalPassword_CsprngReadNumBytesError(t *testing.T) {
// to local storage by initInternalPassword.
func Test_getInternalPassword(t *testing.T) {
externalPassword := "myPassword"
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
rng := csprng.NewSystemRNG()
internalPassword, err := initInternalPassword(
......@@ -196,7 +198,7 @@ func Test_getInternalPassword(t *testing.T) {
// loaded from local storage.
func Test_getInternalPassword_LocalStorageGetPasswordError(t *testing.T) {
externalPassword := "myPassword"
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
ls.Clear()
expectedErr := strings.Split(getPasswordStorageErr, "%")[0]
......@@ -212,9 +214,11 @@ func Test_getInternalPassword_LocalStorageGetPasswordError(t *testing.T) {
// loaded from local storage.
func Test_getInternalPassword_LocalStorageGetError(t *testing.T) {
externalPassword := "myPassword"
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
ls.Clear()
ls.SetItem(passwordKey, []byte("password"))
if err := ls.Set(passwordKey, []byte("password")); err != nil {
t.Fatalf("Failed to set %q: %+v", passwordKey, err)
}
expectedErr := strings.Split(getSaltStorageErr, "%")[0]
......@@ -229,11 +233,17 @@ func Test_getInternalPassword_LocalStorageGetError(t *testing.T) {
// decrypted.
func Test_getInternalPassword_DecryptPasswordError(t *testing.T) {
externalPassword := "myPassword"
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
ls.Clear()
ls.SetItem(saltKey, []byte("salt"))
ls.SetItem(passwordKey, []byte("password"))
ls.SetItem(argonParamsKey, []byte(`{"Time": 1, "Memory": 65536, "Threads": 4}`))
if err := ls.Set(saltKey, []byte("salt")); err != nil {
t.Errorf("failed to set %q: %+v", saltKey, err)
}
if err := ls.Set(passwordKey, []byte("password")); err != nil {
t.Errorf("failed to set %q: %+v", passwordKey, err)
}
if err := ls.Set(argonParamsKey, []byte(`{"Time": 1, "Memory": 65536, "Threads": 4}`)); err != nil {
t.Errorf("failed to set %q: %+v", argonParamsKey, err)
}
expectedErr := strings.Split(decryptPasswordErr, "%")[0]
......
......@@ -10,13 +10,15 @@
package storage
import (
"sync/atomic"
"syscall/js"
"github.com/hack-pad/go-indexeddb/idb"
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/v4/storage/utility"
"gitlab.com/elixxir/xxdk-wasm/utils"
"sync/atomic"
"syscall/js"
"gitlab.com/elixxir/wasm-utils/exception"
"gitlab.com/elixxir/wasm-utils/storage"
)
// numClientsRunning is an atomic that tracks the current number of Cmix
......@@ -57,22 +59,21 @@ func Purge(_ js.Value, args []js.Value) any {
// Check the password
if !verifyPassword(userPassword) {
utils.Throw(utils.TypeError, errors.New("invalid password"))
exception.Throwf("invalid password")
return nil
}
// Verify all Cmix followers are stopped
if n := atomic.LoadUint64(&numClientsRunning); n != 0 {
utils.Throw(utils.TypeError, errors.Errorf(
"%d cMix followers running; all need to be stopped", n))
exception.Throwf("%d cMix followers running; all need to be stopped", n)
return nil
}
// Get all indexedDb database names
databaseList, err := GetIndexedDbList()
if err != nil {
utils.Throw(utils.TypeError, errors.Errorf(
"failed to get list of indexedDb database names: %+v", err))
exception.Throwf(
"failed to get list of indexedDb database names: %+v", err)
return nil
}
jww.DEBUG.Printf("[PURGE] Found %d databases to delete: %s",
......@@ -82,26 +83,34 @@ func Purge(_ js.Value, args []js.Value) any {
for dbName := range databaseList {
_, err = idb.Global().DeleteDatabase(dbName)
if err != nil {
utils.Throw(utils.TypeError, errors.Errorf(
"failed to delete indexedDb database %q: %+v", dbName, err))
exception.Throwf(
"failed to delete indexedDb database %q: %+v", dbName, err)
return nil
}
}
// Get local storage
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
// Clear all local storage saved by this WASM project
n := ls.ClearWASM()
n := ls.Clear()
jww.DEBUG.Printf("[PURGE] Cleared %d WASM keys in local storage", n)
// Clear all EKV from local storage
n = ls.ClearPrefix(storageDirectory)
keys := ls.LocalStorageUNSAFE().KeysPrefix(storageDirectory)
n = len(keys)
for _, keyName := range keys {
ls.LocalStorageUNSAFE().RemoveItem(keyName)
}
jww.DEBUG.Printf("[PURGE] Cleared %d keys with the prefix %q (for EKV)",
n, storageDirectory)
// Clear all NDFs saved to local storage
n = ls.ClearPrefix(utility.NdfStorageKeyNamePrefix)
keys = ls.LocalStorageUNSAFE().KeysPrefix(utility.NdfStorageKeyNamePrefix)
n = len(keys)
for _, keyName := range keys {
ls.LocalStorageUNSAFE().RemoveItem(keyName)
}
jww.DEBUG.Printf("[PURGE] Cleared %d keys with the prefix %q (for NDF)",
n, utility.NdfStorageKeyNamePrefix)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment