beanbagdb 0.5.80 → 0.6.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.
- package/package.json +1 -1
- package/src/index.js +210 -115
- package/src/system_schema.js +207 -153
- package/test/operations.test.js +264 -587
- package/test/pouchdb.js +69 -22
- package/src/plugins/text_command.js +0 -191
- package/test/plugin.test.js +0 -52
package/test/pouchdb.js
CHANGED
|
@@ -3,7 +3,7 @@ import Ajv from 'ajv';
|
|
|
3
3
|
import PouchDB from 'pouchdb';
|
|
4
4
|
import pouchdbFind from 'pouchdb-find';
|
|
5
5
|
PouchDB.plugin(pouchdbFind)
|
|
6
|
-
import
|
|
6
|
+
import crypto from 'crypto'
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
export const get_pdb_doc = (dbname,secret)=>{
|
|
@@ -42,28 +42,75 @@ const pdb = new PouchDB(dbname);
|
|
|
42
42
|
},
|
|
43
43
|
utils: {
|
|
44
44
|
encrypt: async (text, encryptionKey) => {
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
const encoder = new TextEncoder();
|
|
46
|
+
const data = encoder.encode(text); // Encode the text into bytes
|
|
47
|
+
|
|
48
|
+
// Ensure the encryption key is of valid length (16, 24, or 32 bytes for AES-GCM)
|
|
49
|
+
const keyBytes = encoder.encode(encryptionKey);
|
|
50
|
+
if (
|
|
51
|
+
keyBytes.length !== 16 &&
|
|
52
|
+
keyBytes.length !== 24 &&
|
|
53
|
+
keyBytes.length !== 32
|
|
54
|
+
) {
|
|
55
|
+
throw new Error("Encryption key must be 16, 24, or 32 bytes long.");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Convert encryptionKey to CryptoKey
|
|
59
|
+
const key = await crypto.subtle.importKey(
|
|
60
|
+
"raw",
|
|
61
|
+
keyBytes,
|
|
62
|
+
{ name: "AES-GCM" },
|
|
63
|
+
false,
|
|
64
|
+
["encrypt"]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Create a random initialization vector (IV)
|
|
68
|
+
const iv = crypto.getRandomValues(new Uint8Array(12)); // 12 bytes for AES-GCM
|
|
69
|
+
|
|
70
|
+
// Encrypt the data
|
|
71
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
72
|
+
{ name: "AES-GCM", iv: iv },
|
|
73
|
+
key,
|
|
74
|
+
data
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Convert encrypted data and IV to base64 for storage
|
|
78
|
+
const encryptedArray = new Uint8Array(encrypted);
|
|
79
|
+
const encryptedText = btoa(String.fromCharCode(...encryptedArray));
|
|
80
|
+
const ivText = btoa(String.fromCharCode(...iv));
|
|
81
|
+
|
|
82
|
+
return ivText + ":" + encryptedText; // Store IV and encrypted text together
|
|
54
83
|
},
|
|
55
|
-
decrypt: async
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const iv =
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
84
|
+
decrypt: async (encryptedText, encryptionKey) => {
|
|
85
|
+
const [ivText, encryptedData] = encryptedText.split(":");
|
|
86
|
+
|
|
87
|
+
// Convert IV and encrypted data from base64 to byte arrays
|
|
88
|
+
const iv = Uint8Array.from(atob(ivText), (c) => c.charCodeAt(0));
|
|
89
|
+
const encryptedArray = Uint8Array.from(atob(encryptedData), (c) =>
|
|
90
|
+
c.charCodeAt(0)
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const encoder = new TextEncoder();
|
|
94
|
+
|
|
95
|
+
// Convert encryptionKey to CryptoKey
|
|
96
|
+
const key = await crypto.subtle.importKey(
|
|
97
|
+
"raw",
|
|
98
|
+
encoder.encode(encryptionKey),
|
|
99
|
+
{ name: "AES-GCM" },
|
|
100
|
+
false,
|
|
101
|
+
["decrypt"]
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Decrypt the data
|
|
105
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
106
|
+
{ name: "AES-GCM", iv: iv },
|
|
107
|
+
key,
|
|
108
|
+
encryptedArray
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Convert decrypted data back to a string
|
|
112
|
+
const decoder = new TextDecoder();
|
|
113
|
+
return decoder.decode(decrypted);
|
|
67
114
|
},
|
|
68
115
|
ping: () => {
|
|
69
116
|
// @TODO ping the database to check connectivity when class is ready to use
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
const commands = {
|
|
2
|
-
new: {
|
|
3
|
-
parse: async (instance,parts) => {
|
|
4
|
-
let criteria = {}
|
|
5
|
-
criteria.schema = parts.length==0?"":parts.join("")
|
|
6
|
-
return criteria
|
|
7
|
-
},
|
|
8
|
-
run: async (instance,command) => {
|
|
9
|
-
if (command.criteria.schema==""){
|
|
10
|
-
// return a list of all schemas present in the DB
|
|
11
|
-
let all_schema = await instance.get("schema_list")
|
|
12
|
-
return all_schema
|
|
13
|
-
}else{
|
|
14
|
-
// return the schema object for the given schema if not found throw error
|
|
15
|
-
let schema_obj = await instance.search({"selector":{"schema":"schema","data.name":command.criteria.schema}})
|
|
16
|
-
//console.log(schema_obj)
|
|
17
|
-
if(schema_obj.docs.length==0){
|
|
18
|
-
throw new Error("Schema with this name does not exists")
|
|
19
|
-
}
|
|
20
|
-
return schema_obj.docs[0]
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
help: `To create a new record. Format : new/"schema_name(optional)". If no schema name provided, a list of valid schema name is returned`
|
|
24
|
-
},
|
|
25
|
-
open:{
|
|
26
|
-
parse: async (instance,parts) => {
|
|
27
|
-
let criteria = {}
|
|
28
|
-
if (parts.length==0){
|
|
29
|
-
throw new Error("Invalid arguments.open command needs unique id")
|
|
30
|
-
}
|
|
31
|
-
let id_type = parts[0]
|
|
32
|
-
if(id_type=="id"){
|
|
33
|
-
parts.shift()
|
|
34
|
-
criteria["_id"] = parts.join("")
|
|
35
|
-
}else if(id_type=="link"){
|
|
36
|
-
parts.shift()
|
|
37
|
-
criteria["link"] = parts.join("")
|
|
38
|
-
}else if(id_type=="key"){
|
|
39
|
-
parts.shift()
|
|
40
|
-
let text= parts.join()
|
|
41
|
-
let p = text.split(",")
|
|
42
|
-
|
|
43
|
-
p.map(itm=>{
|
|
44
|
-
let p1 = itm.split("=")
|
|
45
|
-
if(p1[0]=="schema"){
|
|
46
|
-
criteria["schema"] = p1[1]
|
|
47
|
-
}else{
|
|
48
|
-
criteria["data"][p1[0]] = p1[1]
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
if(!criteria["schema"]){
|
|
53
|
-
throw new Error("Key requires a schema")
|
|
54
|
-
}
|
|
55
|
-
}else{
|
|
56
|
-
throw new Error("Invalid unique key")
|
|
57
|
-
}
|
|
58
|
-
return criteria
|
|
59
|
-
},
|
|
60
|
-
run: async (instance,command) => {
|
|
61
|
-
try {
|
|
62
|
-
let data = await instance.read(command.criteria,true)
|
|
63
|
-
return data
|
|
64
|
-
} catch (error) {
|
|
65
|
-
throw error
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
help: `To open a record using it's unique id. Format : open/"id||link|key"/"value". In case of key field name must be provided as : field1=value1,fields2=value2...`
|
|
69
|
-
},
|
|
70
|
-
tool:{
|
|
71
|
-
parse : async (instance,parts)=>{
|
|
72
|
-
let criteria = {}
|
|
73
|
-
criteria.type = parts.length==0?"info":parts.join("")
|
|
74
|
-
return criteria
|
|
75
|
-
},
|
|
76
|
-
run : async (instance,command)=>{
|
|
77
|
-
let c_type = command.criteria.type
|
|
78
|
-
let data = {}
|
|
79
|
-
if (c_type=="info"){
|
|
80
|
-
// to get all basic info about the database
|
|
81
|
-
let data = {
|
|
82
|
-
meta: instance.metadata(),
|
|
83
|
-
schemas : {},
|
|
84
|
-
logs:[]
|
|
85
|
-
}
|
|
86
|
-
let schemas = await instance.get("schema_list")
|
|
87
|
-
data.schemas = schemas
|
|
88
|
-
|
|
89
|
-
let logs_doc = await instance.read({"schema":"system_settings","data":{name:"system_logs"}})
|
|
90
|
-
//console.log(logs_doc)
|
|
91
|
-
data =logs_doc.doc.data.value
|
|
92
|
-
return data
|
|
93
|
-
|
|
94
|
-
}else if(c_type=="plugins"){
|
|
95
|
-
// to show list of all plugins installed
|
|
96
|
-
// todo later not implemented yet
|
|
97
|
-
}else if(c_type=="settings"){
|
|
98
|
-
// to show the list of all setting docs available
|
|
99
|
-
let search = instance.search({"selector":{"schema":"system_settings"}})
|
|
100
|
-
return {docs:search.docs}
|
|
101
|
-
}
|
|
102
|
-
else if(c_type=="keys"){
|
|
103
|
-
// to show the list of all keys present in the db
|
|
104
|
-
let search = instance.search({"selector":{"schema":"system_keys"}})
|
|
105
|
-
return {docs:search.docs}
|
|
106
|
-
}
|
|
107
|
-
else{
|
|
108
|
-
throw new Error("Invalid tool command")
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const parse = async (instance, text) => {
|
|
115
|
-
let data = {
|
|
116
|
-
errors: [],
|
|
117
|
-
valid: false,
|
|
118
|
-
name: "",
|
|
119
|
-
criteria: {},
|
|
120
|
-
};
|
|
121
|
-
if (!text) {
|
|
122
|
-
data.errors.push(
|
|
123
|
-
"No text command provided. Format : command_name/parameter"
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
let parts = text.split("/");
|
|
127
|
-
if (parts.length == 0) {
|
|
128
|
-
data.errors.push("Invalid text command");
|
|
129
|
-
}
|
|
130
|
-
let command_name = parts[0];
|
|
131
|
-
if (!commands[command_name]) {
|
|
132
|
-
data.errors.push(
|
|
133
|
-
"Invalid command name. Valid : " + Object.keys(commands).join(",")
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
data.name = command_name;
|
|
137
|
-
try {
|
|
138
|
-
parts.shift();
|
|
139
|
-
let criteria = await commands[command_name].parse(instance,parts);
|
|
140
|
-
data.criteria = criteria;
|
|
141
|
-
} catch (error) {
|
|
142
|
-
data.errors.push(error.message);
|
|
143
|
-
}
|
|
144
|
-
if (data.errors.length == 0) {
|
|
145
|
-
data.valid = true;
|
|
146
|
-
}
|
|
147
|
-
return data;
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const run = async (instance, command) => {
|
|
151
|
-
let data = {
|
|
152
|
-
result:{},
|
|
153
|
-
errors:[],
|
|
154
|
-
valid:false
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
if (!command) {
|
|
158
|
-
data.errors.push("No command object provided ");
|
|
159
|
-
}
|
|
160
|
-
if (!command.valid){
|
|
161
|
-
data.errors["Command cannot be run"]
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
if(!commands[command.name]){
|
|
165
|
-
data.errors["Invalid command name"]
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
try {
|
|
169
|
-
let data1 = await commands[command.name].run(instance,command)
|
|
170
|
-
//console.log(data)
|
|
171
|
-
data.result = data1
|
|
172
|
-
} catch (error) {
|
|
173
|
-
data.errors.push(error.message)
|
|
174
|
-
}
|
|
175
|
-
if(data.errors.length==0){
|
|
176
|
-
data.valid = true
|
|
177
|
-
}
|
|
178
|
-
return data
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const parse_and_run = async(instance, text) => {
|
|
182
|
-
let command = await parse(instance,text)
|
|
183
|
-
console.log(command)
|
|
184
|
-
let command_result = await run(instance,command)
|
|
185
|
-
return command_result
|
|
186
|
-
}
|
|
187
|
-
// const schemas = []
|
|
188
|
-
|
|
189
|
-
export const text_command = {
|
|
190
|
-
actions: {parse,run,parse_and_run}
|
|
191
|
-
};
|
package/test/plugin.test.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// to test database operations. assuming the class is initialized successfully
|
|
2
|
-
// to test initialization of the BeanBagDB class
|
|
3
|
-
import { get_pdb_doc } from "./pouchdb.js";
|
|
4
|
-
import assert, { throws, strictEqual, rejects } from "assert";
|
|
5
|
-
import { BeanBagDB, DocCreationError, EncryptionError, ValidationError,DocNotFoundError, DocUpdateError } from "../src/index.js";
|
|
6
|
-
|
|
7
|
-
import {text_command} from "../src/plugins/text_command.js"
|
|
8
|
-
|
|
9
|
-
import * as chai from 'chai';
|
|
10
|
-
import chaiAsPromised from 'chai-as-promised';
|
|
11
|
-
|
|
12
|
-
chai.use(chaiAsPromised);
|
|
13
|
-
|
|
14
|
-
// Then either:
|
|
15
|
-
const expect = chai.expect;
|
|
16
|
-
|
|
17
|
-
let database; // this is the global db object
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
describe("Testing plugin load", async () => {
|
|
21
|
-
|
|
22
|
-
before(async () => {
|
|
23
|
-
let doc_obj = get_pdb_doc("test_database_30", "qwertyuiopaqwsde1254");
|
|
24
|
-
database = new BeanBagDB(doc_obj);
|
|
25
|
-
await database.ready(); // Ensure the database is ready before running tests
|
|
26
|
-
console.log("Ready for more tests...");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('successfully loads the plugin', async () => {
|
|
30
|
-
try {
|
|
31
|
-
await database.load_plugin("txtcmd",text_command)
|
|
32
|
-
chai.expect(database.plugins).to.not.be.empty
|
|
33
|
-
} catch (error) {
|
|
34
|
-
console.log(error)
|
|
35
|
-
throw error
|
|
36
|
-
}
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('successfully runs the loaded the plugin method', async () => {
|
|
40
|
-
try {
|
|
41
|
-
//await database.load_plugin("txtcmd",text_command)
|
|
42
|
-
let command = await database.plugins["txtcmd"].parse("new/system_keys")
|
|
43
|
-
//console.log(command)
|
|
44
|
-
assert (command.valid ==true)
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.log(error)
|
|
47
|
-
throw error
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
|