Compare commits
12 Commits
spencer/re
...
philip/exp
| Author | SHA1 | Date | |
|---|---|---|---|
|
4cabc22c34
|
|||
|
0fac7246f0
|
|||
|
6dd775051c
|
|||
|
ee37a23ac5
|
|||
|
f53e9a7d2a
|
|||
|
fad9b62539
|
|||
|
a44e2734a4
|
|||
|
52059fe302
|
|||
|
bbba6f48e2
|
|||
|
d71e87198e
|
|||
|
71e864695b
|
|||
|
071cbcc6fb
|
1
apps/cic-meta/.gitignore
vendored
1
apps/cic-meta/.gitignore
vendored
@@ -3,3 +3,4 @@ dist
|
|||||||
dist-web
|
dist-web
|
||||||
dist-server
|
dist-server
|
||||||
scratch
|
scratch
|
||||||
|
tests
|
||||||
|
|||||||
101
apps/cic-meta/package-lock.json
generated
101
apps/cic-meta/package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cic-client-meta",
|
"name": "cic-client-meta",
|
||||||
"version": "0.0.7-alpha.7",
|
"version": "0.0.7-alpha.2",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -852,75 +852,6 @@
|
|||||||
"printj": "~1.1.0"
|
"printj": "~1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"crdt-meta": {
|
|
||||||
"version": "0.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/crdt-meta/-/crdt-meta-0.0.8.tgz",
|
|
||||||
"integrity": "sha512-CS0sS0L2QWthz7vmu6vzl3p4kcpJ+IKILBJ4tbgN4A3iNG8wnBeuDIv/z3KFFQjcfuP4QAh6E9LywKUTxtDc3g==",
|
|
||||||
"requires": {
|
|
||||||
"automerge": "^0.14.2",
|
|
||||||
"ini": "^1.3.8",
|
|
||||||
"openpgp": "^4.10.8",
|
|
||||||
"pg": "^8.5.1",
|
|
||||||
"sqlite3": "^5.0.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"automerge": {
|
|
||||||
"version": "0.14.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/automerge/-/automerge-0.14.2.tgz",
|
|
||||||
"integrity": "sha512-shiwuJHCbNRI23WZyIECLV4Ovf3WiAFJ7P9BH4l5gON1In/UUbjcSJKRygtIirObw2UQumeYxp3F2XBdSvQHnA==",
|
|
||||||
"requires": {
|
|
||||||
"immutable": "^3.8.2",
|
|
||||||
"transit-immutable-js": "^0.7.0",
|
|
||||||
"transit-js": "^0.8.861",
|
|
||||||
"uuid": "^3.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node-addon-api": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw=="
|
|
||||||
},
|
|
||||||
"pg": {
|
|
||||||
"version": "8.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.6.0.tgz",
|
|
||||||
"integrity": "sha512-qNS9u61lqljTDFvmk/N66EeGq3n6Ujzj0FFyNMGQr6XuEv4tgNTXvJQTfJdcvGit5p5/DWPu+wj920hAJFI+QQ==",
|
|
||||||
"requires": {
|
|
||||||
"buffer-writer": "2.0.0",
|
|
||||||
"packet-reader": "1.0.0",
|
|
||||||
"pg-connection-string": "^2.5.0",
|
|
||||||
"pg-pool": "^3.3.0",
|
|
||||||
"pg-protocol": "^1.5.0",
|
|
||||||
"pg-types": "^2.1.0",
|
|
||||||
"pgpass": "1.x"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pg-connection-string": {
|
|
||||||
"version": "2.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
|
|
||||||
"integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
|
|
||||||
},
|
|
||||||
"pg-pool": {
|
|
||||||
"version": "3.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.3.0.tgz",
|
|
||||||
"integrity": "sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg=="
|
|
||||||
},
|
|
||||||
"pg-protocol": {
|
|
||||||
"version": "1.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
|
|
||||||
"integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
|
|
||||||
},
|
|
||||||
"sqlite3": {
|
|
||||||
"version": "5.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.0.2.tgz",
|
|
||||||
"integrity": "sha512-1SdTNo+BVU211Xj1csWa8lV6KM0CtucDwRyA0VHl91wEH1Mgh7RxUpI4rVvG7OhHrzCSGaVyW5g8vKvlrk9DJA==",
|
|
||||||
"requires": {
|
|
||||||
"node-addon-api": "^3.0.0",
|
|
||||||
"node-gyp": "3.x",
|
|
||||||
"node-pre-gyp": "^0.11.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"create-hash": {
|
"create-hash": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
||||||
@@ -1035,17 +966,17 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"elliptic": {
|
"elliptic": {
|
||||||
"version": "6.5.4",
|
"version": "6.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||||
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.11.9",
|
"bn.js": "^4.4.0",
|
||||||
"brorand": "^1.1.0",
|
"brorand": "^1.0.1",
|
||||||
"hash.js": "^1.0.0",
|
"hash.js": "^1.0.0",
|
||||||
"hmac-drbg": "^1.0.1",
|
"hmac-drbg": "^1.0.0",
|
||||||
"inherits": "^2.0.4",
|
"inherits": "^2.0.1",
|
||||||
"minimalistic-assert": "^1.0.1",
|
"minimalistic-assert": "^1.0.0",
|
||||||
"minimalistic-crypto-utils": "^1.0.1"
|
"minimalistic-crypto-utils": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"emoji-regex": {
|
"emoji-regex": {
|
||||||
@@ -1558,9 +1489,9 @@
|
|||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.8",
|
"version": "1.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
||||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
|
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
|
||||||
},
|
},
|
||||||
"interpret": {
|
"interpret": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
@@ -2026,9 +1957,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"y18n": {
|
"y18n": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
||||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"yargs": {
|
"yargs": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cic-client-meta",
|
"name": "cic-client-meta",
|
||||||
"version": "0.0.7-alpha.8",
|
"version": "0.0.7-alpha.6",
|
||||||
"description": "Signed CRDT metadata graphs for the CIC network",
|
"description": "Signed CRDT metadata graphs for the CIC network",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
@@ -15,9 +15,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ethereumjs/tx": "^3.0.0-beta.1",
|
"@ethereumjs/tx": "^3.0.0-beta.1",
|
||||||
"automerge": "^0.14.1",
|
"automerge": "^0.14.1",
|
||||||
"crdt-meta": "0.0.8",
|
|
||||||
"ethereumjs-wallet": "^1.0.1",
|
"ethereumjs-wallet": "^1.0.1",
|
||||||
"ini": "^1.3.8",
|
"ini": "^1.3.5",
|
||||||
"openpgp": "^4.10.8",
|
"openpgp": "^4.10.8",
|
||||||
"pg": "^8.4.2",
|
"pg": "^8.4.2",
|
||||||
"sqlite3": "^5.0.0",
|
"sqlite3": "^5.0.0",
|
||||||
@@ -41,6 +40,6 @@
|
|||||||
],
|
],
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.16.1"
|
"node": "~14.16.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Config } from 'crdt-meta';
|
const config = require('./src/config');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
if (process.argv[2] === undefined) {
|
if (process.argv[2] === undefined) {
|
||||||
@@ -15,6 +15,6 @@ try {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const c = new Config(process.argv[2], process.env['CONFINI_ENV_PREFIX']);
|
const c = new config.Config(process.argv[2], process.env['CONFINI_ENV_PREFIX']);
|
||||||
c.process();
|
c.process();
|
||||||
process.stdout.write(c.toString());
|
process.stdout.write(c.toString());
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import * as Automerge from 'automerge';
|
import * as Automerge from 'automerge';
|
||||||
import * as pgp from 'openpgp';
|
import * as pgp from 'openpgp';
|
||||||
|
import * as pg from 'pg';
|
||||||
|
|
||||||
import { Envelope, Syncable } from 'crdt-meta';
|
import { Envelope, Syncable } from '../../src/sync';
|
||||||
|
|
||||||
|
|
||||||
function handleNoMergeGet(db, digest, keystore) {
|
function handleNoMergeGet(db, digest, keystore) {
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import * as pgp from 'openpgp';
|
||||||
|
|
||||||
import * as handlers from './handlers';
|
import * as handlers from './handlers';
|
||||||
import { PGPKeyStore, PGPSigner, Config, SqliteAdapter, PostgresAdapter } from 'crdt-meta';
|
import { Envelope, Syncable } from '../../src/sync';
|
||||||
|
import { PGPKeyStore, PGPSigner } from '../../src/auth';
|
||||||
|
|
||||||
import { standardArgs } from './args';
|
import { standardArgs } from './args';
|
||||||
|
import { Config } from '../../src/config';
|
||||||
|
import { SqliteAdapter, PostgresAdapter } from '../../src/db';
|
||||||
|
|
||||||
let configPath = '/usr/local/etc/cic-meta';
|
let configPath = '/usr/local/etc/cic-meta';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Syncable, Addressable, mergeKey } from 'crdt-meta';
|
import { ArgPair, Syncable } from '../sync';
|
||||||
|
import { Addressable, mergeKey } from '../digest';
|
||||||
|
|
||||||
class Phone extends Syncable implements Addressable {
|
class Phone extends Syncable implements Addressable {
|
||||||
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Syncable, Addressable, toAddressKey } from 'crdt-meta';
|
import { ArgPair, Syncable } from '../sync';
|
||||||
|
import { Addressable, addressToBytes, bytesToHex, toAddressKey } from '../digest';
|
||||||
|
|
||||||
const keySalt = new TextEncoder().encode(':cic.person');
|
const keySalt = new TextEncoder().encode(':cic.person');
|
||||||
class User extends Syncable implements Addressable {
|
class User extends Syncable implements Addressable {
|
||||||
191
apps/cic-meta/src/auth.ts
Normal file
191
apps/cic-meta/src/auth.ts
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import * as pgp from 'openpgp';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
|
|
||||||
|
interface Signable {
|
||||||
|
digest():string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyGetter = () => any;
|
||||||
|
|
||||||
|
type Signature = {
|
||||||
|
engine:string
|
||||||
|
algo:string
|
||||||
|
data:string
|
||||||
|
digest:string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Signer {
|
||||||
|
prepare(Signable):boolean;
|
||||||
|
onsign(Signature):void;
|
||||||
|
onverify(boolean):void;
|
||||||
|
sign(digest:string):void
|
||||||
|
verify(digest:string, signature:Signature):void
|
||||||
|
fingerprint():string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Authoritative {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KeyStore {
|
||||||
|
getPrivateKey: KeyGetter
|
||||||
|
getFingerprint: () => string
|
||||||
|
getTrustedKeys: () => Array<any>
|
||||||
|
getTrustedActiveKeys: () => Array<any>
|
||||||
|
getEncryptKeys: () => Array<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
class PGPKeyStore implements KeyStore {
|
||||||
|
|
||||||
|
fingerprint: string
|
||||||
|
pk: any
|
||||||
|
|
||||||
|
pubk = {
|
||||||
|
active: [],
|
||||||
|
trusted: [],
|
||||||
|
encrypt: [],
|
||||||
|
}
|
||||||
|
loads = 0x00;
|
||||||
|
loadsTarget = 0x0f;
|
||||||
|
onload: (k:KeyStore) => void;
|
||||||
|
|
||||||
|
constructor(passphrase:string, pkArmor:string, pubkActiveArmor:string, pubkTrustedArmor:string, pubkEncryptArmor:string, onload = (ks:KeyStore) => {}) {
|
||||||
|
this._readKey(pkArmor, undefined, 1, passphrase);
|
||||||
|
this._readKey(pubkActiveArmor, 'active', 2);
|
||||||
|
this._readKey(pubkTrustedArmor, 'trusted', 4);
|
||||||
|
this._readKey(pubkEncryptArmor, 'encrypt', 8);
|
||||||
|
this.onload = onload;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _readKey(a:string, x:any, n:number, pass?:string) {
|
||||||
|
pgp.key.readArmored(a).then((k) => {
|
||||||
|
if (pass !== undefined) {
|
||||||
|
this.pk = k.keys[0];
|
||||||
|
this.pk.decrypt(pass).then(() => {
|
||||||
|
this.fingerprint = this.pk.getFingerprint();
|
||||||
|
console.log('private key (sign)', this.fingerprint);
|
||||||
|
this._registerLoad(n);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.pubk[x] = k.keys;
|
||||||
|
k.keys.forEach((pubk) => {
|
||||||
|
console.log('public key (' + x + ')', pubk.getFingerprint());
|
||||||
|
});
|
||||||
|
this._registerLoad(n);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _registerLoad(b:number) {
|
||||||
|
this.loads |= b;
|
||||||
|
if (this.loads == this.loadsTarget) {
|
||||||
|
this.onload(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTrustedKeys(): Array<any> {
|
||||||
|
return this.pubk['trusted'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTrustedActiveKeys(): Array<any> {
|
||||||
|
return this.pubk['active'];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public getEncryptKeys(): Array<any> {
|
||||||
|
return this.pubk['encrypt'];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPrivateKey(): any {
|
||||||
|
return this.pk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFingerprint(): string {
|
||||||
|
return this.fingerprint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PGPSigner implements Signer {
|
||||||
|
|
||||||
|
engine = 'pgp'
|
||||||
|
algo = 'sha256'
|
||||||
|
dgst: string
|
||||||
|
signature: Signature
|
||||||
|
keyStore: KeyStore
|
||||||
|
onsign: (Signature) => void
|
||||||
|
onverify: (boolean) => void
|
||||||
|
|
||||||
|
constructor(keyStore:KeyStore) {
|
||||||
|
this.keyStore = keyStore
|
||||||
|
this.onsign = (string) => {};
|
||||||
|
this.onverify = (boolean) => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public fingerprint(): string {
|
||||||
|
return this.keyStore.getFingerprint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public prepare(material:Signable):boolean {
|
||||||
|
this.dgst = material.digest();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public verify(digest:string, signature:Signature) {
|
||||||
|
pgp.signature.readArmored(signature.data).then((s) => {
|
||||||
|
const opts = {
|
||||||
|
message: pgp.cleartext.fromText(digest),
|
||||||
|
publicKeys: this.keyStore.getTrustedKeys(),
|
||||||
|
signature: s,
|
||||||
|
};
|
||||||
|
pgp.verify(opts).then((v) => {
|
||||||
|
let i = 0;
|
||||||
|
for (i = 0; i < v.signatures.length; i++) {
|
||||||
|
const s = v.signatures[i];
|
||||||
|
if (s.valid) {
|
||||||
|
this.onverify(s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.error('checked ' + i + ' signature(s) but none valid');
|
||||||
|
this.onverify(false);
|
||||||
|
});
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
this.onverify(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public sign(digest:string) {
|
||||||
|
const m = pgp.cleartext.fromText(digest);
|
||||||
|
const pk = this.keyStore.getPrivateKey();
|
||||||
|
const opts = {
|
||||||
|
message: m,
|
||||||
|
privateKeys: [pk],
|
||||||
|
detached: true,
|
||||||
|
}
|
||||||
|
pgp.sign(opts).then((s) => {
|
||||||
|
this.signature = {
|
||||||
|
engine: this.engine,
|
||||||
|
algo: this.algo,
|
||||||
|
data: s.signature,
|
||||||
|
// TODO: fix for browser later
|
||||||
|
digest: digest,
|
||||||
|
};
|
||||||
|
this.onsign(this.signature);
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
this.onsign(undefined);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Signature,
|
||||||
|
Authoritative,
|
||||||
|
Signer,
|
||||||
|
KeyGetter,
|
||||||
|
Signable,
|
||||||
|
KeyStore,
|
||||||
|
PGPSigner,
|
||||||
|
PGPKeyStore,
|
||||||
|
};
|
||||||
71
apps/cic-meta/src/config.ts
Normal file
71
apps/cic-meta/src/config.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as ini from 'ini';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
class Config {
|
||||||
|
|
||||||
|
filepath: string
|
||||||
|
store: Object
|
||||||
|
censor: Array<string>
|
||||||
|
require: Array<string>
|
||||||
|
env_prefix: string
|
||||||
|
|
||||||
|
constructor(filepath:string, env_prefix?:string) {
|
||||||
|
this.filepath = filepath;
|
||||||
|
this.store = {};
|
||||||
|
this.censor = [];
|
||||||
|
this.require = [];
|
||||||
|
this.env_prefix = '';
|
||||||
|
if (env_prefix !== undefined) {
|
||||||
|
this.env_prefix = env_prefix + "_";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public process() {
|
||||||
|
const d = fs.readdirSync(this.filepath);
|
||||||
|
|
||||||
|
const r = /.*\.ini$/;
|
||||||
|
for (let i = 0; i < d.length; i++) {
|
||||||
|
const f = d[i];
|
||||||
|
if (!f.match(r)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fp = path.join(this.filepath, f);
|
||||||
|
const v = fs.readFileSync(fp, 'utf-8');
|
||||||
|
const inid = ini.decode(v);
|
||||||
|
const inik = Object.keys(inid);
|
||||||
|
for (let j = 0; j < inik.length; j++) {
|
||||||
|
const k_section = inik[j]
|
||||||
|
const k = k_section.toUpperCase();
|
||||||
|
Object.keys(inid[k_section]).forEach((k_directive) => {
|
||||||
|
const kk = k_directive.toUpperCase();
|
||||||
|
const kkk = k + '_' + kk;
|
||||||
|
|
||||||
|
let r = inid[k_section][k_directive];
|
||||||
|
const k_env = this.env_prefix + kkk
|
||||||
|
const env = process.env[k_env];
|
||||||
|
if (env !== undefined) {
|
||||||
|
console.debug('Environment variable ' + k_env + ' overrides ' + kkk);
|
||||||
|
r = env;
|
||||||
|
}
|
||||||
|
this.store[kkk] = r;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(s:string) {
|
||||||
|
return this.store[s];
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString() {
|
||||||
|
let s = '';
|
||||||
|
Object.keys(this.store).forEach((k) => {
|
||||||
|
s += k + '=' + this.store[k] + '\n';
|
||||||
|
});
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Config };
|
||||||
38
apps/cic-meta/src/constants.ts
Normal file
38
apps/cic-meta/src/constants.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { JSONSerializable } from './format';
|
||||||
|
|
||||||
|
const ENGINE_NAME = 'automerge';
|
||||||
|
const ENGINE_VERSION = '0.14.1';
|
||||||
|
|
||||||
|
const NETWORK_NAME = 'cic';
|
||||||
|
const NETWORK_VERSION = '1';
|
||||||
|
|
||||||
|
const CRYPTO_NAME = 'pgp';
|
||||||
|
const CRYPTO_VERSION = '2';
|
||||||
|
|
||||||
|
type VersionedSpec = {
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
ext?: Object
|
||||||
|
}
|
||||||
|
|
||||||
|
const engineSpec:VersionedSpec = {
|
||||||
|
name: ENGINE_NAME,
|
||||||
|
version: ENGINE_VERSION,
|
||||||
|
}
|
||||||
|
|
||||||
|
const cryptoSpec:VersionedSpec = {
|
||||||
|
name: CRYPTO_NAME,
|
||||||
|
version: CRYPTO_VERSION,
|
||||||
|
}
|
||||||
|
|
||||||
|
const networkSpec:VersionedSpec = {
|
||||||
|
name: NETWORK_NAME,
|
||||||
|
version: NETWORK_VERSION,
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
engineSpec,
|
||||||
|
cryptoSpec,
|
||||||
|
networkSpec,
|
||||||
|
VersionedSpec,
|
||||||
|
};
|
||||||
27
apps/cic-meta/src/crypto.ts
Normal file
27
apps/cic-meta/src/crypto.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import * as crypto from 'crypto';
|
||||||
|
|
||||||
|
const _algs = {
|
||||||
|
'SHA-256': 'sha256',
|
||||||
|
}
|
||||||
|
|
||||||
|
function cryptoWrapper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
cryptoWrapper.prototype.digest = async function(s, d) {
|
||||||
|
const h = crypto.createHash(_algs[s]);
|
||||||
|
h.update(d);
|
||||||
|
return h.digest();
|
||||||
|
}
|
||||||
|
|
||||||
|
let subtle = undefined;
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
subtle = window.crypto.subtle;
|
||||||
|
} else {
|
||||||
|
subtle = new cryptoWrapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
subtle,
|
||||||
|
}
|
||||||
|
|
||||||
90
apps/cic-meta/src/db.ts
Normal file
90
apps/cic-meta/src/db.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import * as pg from 'pg';
|
||||||
|
import * as sqlite from 'sqlite3';
|
||||||
|
|
||||||
|
type DbConfig = {
|
||||||
|
name: string
|
||||||
|
host: string
|
||||||
|
port: number
|
||||||
|
user: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DbAdapter {
|
||||||
|
query: (s:string, callback:(e:any, rs:any) => void) => void
|
||||||
|
close: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const re_creatematch = /^(CREATE)/i
|
||||||
|
const re_getmatch = /^(SELECT)/i;
|
||||||
|
const re_setmatch = /^(INSERT|UPDATE)/i;
|
||||||
|
|
||||||
|
class SqliteAdapter implements DbAdapter {
|
||||||
|
|
||||||
|
db: any
|
||||||
|
|
||||||
|
constructor(dbConfig:DbConfig, callback?:(any) => void) {
|
||||||
|
this.db = new sqlite.Database(dbConfig.name); //, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public query(s:string, callback:(e:any, rs?:any) => void): void {
|
||||||
|
const local_callback = (e, rs) => {
|
||||||
|
let r = undefined;
|
||||||
|
if (rs !== undefined) {
|
||||||
|
r = {
|
||||||
|
rowCount: rs.length,
|
||||||
|
rows: rs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback(e, r);
|
||||||
|
};
|
||||||
|
if (s.match(re_getmatch)) {
|
||||||
|
this.db.all(s, local_callback);
|
||||||
|
} else if (s.match(re_setmatch)) {
|
||||||
|
this.db.run(s, local_callback);
|
||||||
|
} else if (s.match(re_creatematch)) {
|
||||||
|
this.db.run(s, callback);
|
||||||
|
} else {
|
||||||
|
throw 'unhandled query';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public close() {
|
||||||
|
this.db.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostgresAdapter implements DbAdapter {
|
||||||
|
|
||||||
|
db: any
|
||||||
|
|
||||||
|
constructor(dbConfig:DbConfig) {
|
||||||
|
let o = dbConfig;
|
||||||
|
o['database'] = o.name;
|
||||||
|
this.db = new pg.Pool(o);
|
||||||
|
return this.db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public query(s:string, callback:(e:any, rs:any) => void): void {
|
||||||
|
this.db.query(s, (e, rs) => {
|
||||||
|
let r = {
|
||||||
|
length: rs.rowCount,
|
||||||
|
}
|
||||||
|
rs.length = rs.rowCount;
|
||||||
|
if (e === undefined) {
|
||||||
|
e = null;
|
||||||
|
}
|
||||||
|
console.debug(e, rs);
|
||||||
|
callback(e, rs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public close() {
|
||||||
|
this.db.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
DbConfig,
|
||||||
|
SqliteAdapter,
|
||||||
|
PostgresAdapter,
|
||||||
|
}
|
||||||
68
apps/cic-meta/src/digest.ts
Normal file
68
apps/cic-meta/src/digest.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import * as crypto from './crypto';
|
||||||
|
|
||||||
|
interface Addressable {
|
||||||
|
key(): string
|
||||||
|
digest(): string
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringToBytes(s:string) {
|
||||||
|
const a = new Uint8Array(20);
|
||||||
|
let j = 2;
|
||||||
|
for (let i = 0; i < a.byteLength; i++) {
|
||||||
|
const n = parseInt(s.substring(j, j+2), 16);
|
||||||
|
a[i] = n;
|
||||||
|
j += 2;
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bytesToHex(a:Uint8Array) {
|
||||||
|
let s = '';
|
||||||
|
for (let i = 0; i < a.byteLength; i++) {
|
||||||
|
const h = '00' + a[i].toString(16);
|
||||||
|
s += h.slice(-2);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mergeKey(a:Uint8Array, s:Uint8Array) {
|
||||||
|
const y = new Uint8Array(a.byteLength + s.byteLength);
|
||||||
|
for (let i = 0; i < a.byteLength; i++) {
|
||||||
|
y[i] = a[i];
|
||||||
|
}
|
||||||
|
for (let i = 0; i < s.byteLength; i++) {
|
||||||
|
y[a.byteLength + i] = s[i];
|
||||||
|
}
|
||||||
|
const z = await crypto.subtle.digest('SHA-256', y);
|
||||||
|
return bytesToHex(new Uint8Array(z));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toKey(v:string, salt:string) {
|
||||||
|
const a = stringToBytes(v);
|
||||||
|
const s = new TextEncoder().encode(salt);
|
||||||
|
return await mergeKey(a, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function toAddressKey(zeroExHex:string, salt:string) {
|
||||||
|
const a = addressToBytes(zeroExHex);
|
||||||
|
const s = new TextEncoder().encode(salt);
|
||||||
|
return await mergeKey(a, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
const re_addrHex = /^0[xX][a-fA-F0-9]{40}$/;
|
||||||
|
function addressToBytes(s:string) {
|
||||||
|
if (!s.match(re_addrHex)) {
|
||||||
|
throw 'invalid address hex';
|
||||||
|
}
|
||||||
|
return stringToBytes(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
toKey,
|
||||||
|
toAddressKey,
|
||||||
|
mergeKey,
|
||||||
|
bytesToHex,
|
||||||
|
addressToBytes,
|
||||||
|
Addressable,
|
||||||
|
}
|
||||||
58
apps/cic-meta/src/dispatch.ts
Normal file
58
apps/cic-meta/src/dispatch.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { Syncable } from './sync';
|
||||||
|
import { Store } from './store';
|
||||||
|
import { PubSub } from './transport';
|
||||||
|
|
||||||
|
function toIndexKey(id:string):string {
|
||||||
|
const d = Date.now();
|
||||||
|
return d + '_' + id + '_' + uuidv4();
|
||||||
|
}
|
||||||
|
|
||||||
|
const _re_indexKey = /^\d+_(.+)_[-\d\w]+$/;
|
||||||
|
function fromIndexKey(s:string):string {
|
||||||
|
const m = s.match(_re_indexKey);
|
||||||
|
if (m === null) {
|
||||||
|
throw 'Invalid index key';
|
||||||
|
}
|
||||||
|
return m[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
class Dispatcher {
|
||||||
|
|
||||||
|
idx: Array<string>
|
||||||
|
syncer: PubSub
|
||||||
|
store: Store
|
||||||
|
|
||||||
|
constructor(store:Store, syncer:PubSub) {
|
||||||
|
this.idx = new Array<string>()
|
||||||
|
this.syncer = syncer;
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isDirty(): boolean {
|
||||||
|
return this.idx.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public add(id:string, item:Syncable): string {
|
||||||
|
const v = item.toJSON();
|
||||||
|
const k = toIndexKey(id);
|
||||||
|
this.store.put(k, v, true);
|
||||||
|
localStorage.setItem(k, v);
|
||||||
|
this.idx.push(k);
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sync(offset:number): number {
|
||||||
|
let i = 0;
|
||||||
|
this.idx.forEach((k) => {
|
||||||
|
const v = localStorage.getItem(k);
|
||||||
|
const k_id = fromIndexKey(k);
|
||||||
|
this.syncer.pub(v); // this must block until guaranteed delivery
|
||||||
|
localStorage.removeItem(k);
|
||||||
|
i++;
|
||||||
|
});
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Dispatcher, toIndexKey, fromIndexKey }
|
||||||
5
apps/cic-meta/src/format.ts
Normal file
5
apps/cic-meta/src/format.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
interface JSONSerializable {
|
||||||
|
toJSON(): string
|
||||||
|
}
|
||||||
|
|
||||||
|
export { JSONSerializable };
|
||||||
@@ -1,2 +1,5 @@
|
|||||||
export { User } from './user';
|
export { PGPSigner, PGPKeyStore, Signer, KeyStore } from './auth';
|
||||||
export { Phone } from './phone';
|
export { ArgPair, Envelope, Syncable } from './sync';
|
||||||
|
export { User } from './assets/user';
|
||||||
|
export { Phone } from './assets/phone';
|
||||||
|
export { Config } from './config';
|
||||||
|
|||||||
9
apps/cic-meta/src/store.ts
Normal file
9
apps/cic-meta/src/store.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Syncable } from './sync';
|
||||||
|
|
||||||
|
interface Store {
|
||||||
|
put(string, Syncable, boolean?)
|
||||||
|
get(string):Syncable
|
||||||
|
delete(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Store };
|
||||||
266
apps/cic-meta/src/sync.ts
Normal file
266
apps/cic-meta/src/sync.ts
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
import * as Automerge from 'automerge';
|
||||||
|
|
||||||
|
import { JSONSerializable } from './format';
|
||||||
|
|
||||||
|
import { Authoritative, Signer, PGPSigner, Signable, Signature } from './auth';
|
||||||
|
|
||||||
|
import { engineSpec, cryptoSpec, networkSpec, VersionedSpec } from './constants';
|
||||||
|
|
||||||
|
const fullSpec:VersionedSpec = {
|
||||||
|
name: 'cic',
|
||||||
|
version: '1',
|
||||||
|
ext: {
|
||||||
|
network: cryptoSpec,
|
||||||
|
engine: engineSpec,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
class Envelope {
|
||||||
|
|
||||||
|
o = fullSpec
|
||||||
|
|
||||||
|
constructor(payload:Object) {
|
||||||
|
this.set(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public set(payload:Object) {
|
||||||
|
this.o['payload'] = payload
|
||||||
|
}
|
||||||
|
|
||||||
|
public get():string {
|
||||||
|
return this.o['payload'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public toJSON() {
|
||||||
|
return JSON.stringify(this.o);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromJSON(s:string): Envelope {
|
||||||
|
const e = new Envelope(undefined);
|
||||||
|
e.o = JSON.parse(s);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unwrap(): Syncable {
|
||||||
|
return Syncable.fromJSON(this.o['payload']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArgPair {
|
||||||
|
|
||||||
|
k:string
|
||||||
|
v:any
|
||||||
|
|
||||||
|
constructor(k:string, v:any) {
|
||||||
|
this.k = k;
|
||||||
|
this.v = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SignablePart implements Signable {
|
||||||
|
|
||||||
|
s: string
|
||||||
|
|
||||||
|
constructor(s:string) {
|
||||||
|
this.s = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public digest():string {
|
||||||
|
return this.s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function orderDict(src) {
|
||||||
|
let dst;
|
||||||
|
if (Array.isArray(src)) {
|
||||||
|
dst = [];
|
||||||
|
src.forEach((v) => {
|
||||||
|
if (typeof(v) == 'object') {
|
||||||
|
v = orderDict(v);
|
||||||
|
}
|
||||||
|
dst.push(v);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dst = {}
|
||||||
|
Object.keys(src).sort().forEach((k) => {
|
||||||
|
let v = src[k];
|
||||||
|
if (typeof(v) == 'object') {
|
||||||
|
v = orderDict(v);
|
||||||
|
}
|
||||||
|
dst[k] = v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Syncable implements JSONSerializable, Authoritative, Signable {
|
||||||
|
|
||||||
|
id: string
|
||||||
|
timestamp: number
|
||||||
|
m: any // automerge object
|
||||||
|
e: Envelope
|
||||||
|
signer: Signer
|
||||||
|
onwrap: (string) => void
|
||||||
|
onauthenticate: (boolean) => void
|
||||||
|
|
||||||
|
// TODO: Move data to sub-object so timestamp, id, signature don't collide
|
||||||
|
constructor(id:string, v:Object) {
|
||||||
|
this.id = id;
|
||||||
|
const o = {
|
||||||
|
'id': id,
|
||||||
|
'timestamp': Math.floor(Date.now() / 1000),
|
||||||
|
'data': v,
|
||||||
|
}
|
||||||
|
//this.m = Automerge.from(v)
|
||||||
|
this.m = Automerge.from(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
public setSigner(signer:Signer) {
|
||||||
|
this.signer = signer;
|
||||||
|
this.signer.onsign = (s) => {
|
||||||
|
this.wrap(s);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: To keep integrity, the non-link key/value pairs for each step also need to be hashed
|
||||||
|
public digest(): string {
|
||||||
|
const links = [];
|
||||||
|
Automerge.getAllChanges(this.m).forEach((ch:Object) => {
|
||||||
|
const op:Array<any> = ch['ops'];
|
||||||
|
ch['ops'].forEach((op:Array<Object>) => {
|
||||||
|
if (op['action'] == 'link') {
|
||||||
|
//console.log('op link', op);
|
||||||
|
links.push([op['obj'], op['value']]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//return JSON.stringify(links);
|
||||||
|
const j = JSON.stringify(links);
|
||||||
|
return Buffer.from(j).toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
private wrap(s:any) {
|
||||||
|
this.m = Automerge.change(this.m, 'sign', (doc) => {
|
||||||
|
doc['signature'] = s;
|
||||||
|
});
|
||||||
|
this.e = new Envelope(this.toJSON());
|
||||||
|
console.log('wrappin s', s, typeof(s));
|
||||||
|
this.e.o['digest'] = s.digest;
|
||||||
|
if (this.onwrap !== undefined) {
|
||||||
|
this.onwrap(this.e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// private _verifyLoop(i:number, history:Array<any>, signable:Signable, result:boolean) {
|
||||||
|
// if (!result) {
|
||||||
|
// this.onauthenticate(false);
|
||||||
|
// return;
|
||||||
|
// } else if (history.length == 0) {
|
||||||
|
// this.onauthenticate(true);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// const h = history.shift()
|
||||||
|
// if (i % 2 == 0) {
|
||||||
|
// i++;
|
||||||
|
// signable = {
|
||||||
|
// digest: () => {
|
||||||
|
// return Automerge.save(h.snapshot)
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
// this._verifyLoop(i, history, signable, true);
|
||||||
|
// } else {
|
||||||
|
// i++;
|
||||||
|
// const signature = h.snapshot['signature'];
|
||||||
|
// console.debug('signature', signature, signable.digest());
|
||||||
|
// this.signer.onverify = (v) => {
|
||||||
|
// this._verifyLoop(i, history, signable, v)
|
||||||
|
// }
|
||||||
|
// this.signer.verify(signable, signature);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // TODO: This should replay the graph and check signatures on each step
|
||||||
|
// public _authenticate(full:boolean=false) {
|
||||||
|
// let h = Automerge.getHistory(this.m);
|
||||||
|
// h.forEach((m) => {
|
||||||
|
// //console.debug(m.snapshot);
|
||||||
|
// });
|
||||||
|
// const signable = {
|
||||||
|
// digest: () => { return '' },
|
||||||
|
// }
|
||||||
|
// if (!full) {
|
||||||
|
// h = h.slice(h.length-2);
|
||||||
|
// }
|
||||||
|
// this._verifyLoop(0, h, signable, true);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public authenticate(full:boolean=false) {
|
||||||
|
if (full) {
|
||||||
|
console.warn('only doing shallow authentication for now, sorry');
|
||||||
|
}
|
||||||
|
//console.log('authenticating', signable.digest());
|
||||||
|
//console.log('signature', this.m.signature);
|
||||||
|
this.signer.onverify = (v) => {
|
||||||
|
//this._verifyLoop(i, history, signable, v)
|
||||||
|
this.onauthenticate(v);
|
||||||
|
}
|
||||||
|
this.signer.verify(this.m.signature.digest, this.m.signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public sign() {
|
||||||
|
//this.signer.prepare(this);
|
||||||
|
this.signer.sign(this.digest());
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(changes:Array<ArgPair>, changesDescription:string) {
|
||||||
|
this.m = Automerge.change(this.m, changesDescription, (m) => {
|
||||||
|
changes.forEach((c) => {
|
||||||
|
let path = c.k.split('.');
|
||||||
|
let target = m['data'];
|
||||||
|
while (path.length > 1) {
|
||||||
|
const part = path.shift();
|
||||||
|
target = target[part];
|
||||||
|
}
|
||||||
|
target[path[0]] = c.v;
|
||||||
|
});
|
||||||
|
m['timestamp'] = Math.floor(Date.now() / 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public replace(o:Object, changesDescription:string) {
|
||||||
|
this.m = Automerge.change(this.m, changesDescription, (m) => {
|
||||||
|
Object.keys(o).forEach((k) => {
|
||||||
|
m['data'][k] = o[k];
|
||||||
|
});
|
||||||
|
Object.keys(m).forEach((k) => {
|
||||||
|
if (o[k] == undefined) {
|
||||||
|
delete m['data'][k];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
m['timestamp'] = Math.floor(Date.now() / 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public merge(s:Syncable) {
|
||||||
|
this.m = Automerge.merge(s.m, this.m);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toJSON(): string {
|
||||||
|
const s = Automerge.save(this.m);
|
||||||
|
const o = JSON.parse(s);
|
||||||
|
const oo = orderDict(o)
|
||||||
|
return JSON.stringify(oo);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromJSON(s:string): Syncable {
|
||||||
|
const doc = Automerge.load(s);
|
||||||
|
let y = new Syncable(doc['id'], {});
|
||||||
|
y.m = doc
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { JSONSerializable, Syncable, ArgPair, Envelope };
|
||||||
11
apps/cic-meta/src/transport.ts
Normal file
11
apps/cic-meta/src/transport.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
interface SubConsumer {
|
||||||
|
post(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PubSub {
|
||||||
|
pub(v:string):boolean
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
export { PubSub, SubConsumer };
|
||||||
|
|
||||||
50
apps/cic-meta/tests/1_basic.ts
Normal file
50
apps/cic-meta/tests/1_basic.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import * as Automerge from 'automerge';
|
||||||
|
import assert = require('assert');
|
||||||
|
|
||||||
|
import { Dispatcher, toIndexKey, fromIndexKey } from '../src/dispatch';
|
||||||
|
import { User } from '../src/assets/user';
|
||||||
|
import { Syncable, ArgPair } from '../src/sync';
|
||||||
|
|
||||||
|
import { MockSigner, MockStore } from './mock';
|
||||||
|
|
||||||
|
describe('basic', () => {
|
||||||
|
|
||||||
|
it('store', () => {
|
||||||
|
const store = new MockStore('s');
|
||||||
|
assert.equal(store.name, 's');
|
||||||
|
|
||||||
|
const mockSigner = new MockSigner();
|
||||||
|
const v = new Syncable('foo', {baz: 42});
|
||||||
|
v.setSigner(mockSigner);
|
||||||
|
store.put('foo', v);
|
||||||
|
const one = store.get('foo').toJSON();
|
||||||
|
const vv = new Syncable('bar', {baz: 666});
|
||||||
|
vv.setSigner(mockSigner);
|
||||||
|
assert.throws(() => {
|
||||||
|
store.put('foo', vv)
|
||||||
|
});
|
||||||
|
store.put('foo', vv, true);
|
||||||
|
const other = store.get('foo').toJSON();
|
||||||
|
assert.notEqual(one, other);
|
||||||
|
store.delete('foo');
|
||||||
|
assert.equal(store.get('foo'), undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('add_doc_to_dispatcher', () => {
|
||||||
|
const store = new MockStore('s');
|
||||||
|
//const syncer = new MockSyncer();
|
||||||
|
const dispatcher = new Dispatcher(store, undefined);
|
||||||
|
const user = new User('foo');
|
||||||
|
dispatcher.add(user.id, user);
|
||||||
|
assert(dispatcher.isDirty());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatch_keyindex', () => {
|
||||||
|
const s = 'foo';
|
||||||
|
const k = toIndexKey(s);
|
||||||
|
const v = fromIndexKey(k);
|
||||||
|
assert.equal(s, v);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
212
apps/cic-meta/tests/2_sync.ts
Normal file
212
apps/cic-meta/tests/2_sync.ts
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
import * as Automerge from 'automerge';
|
||||||
|
import assert = require('assert');
|
||||||
|
|
||||||
|
import * as pgp from 'openpgp';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
import { PGPSigner } from '../src/auth';
|
||||||
|
|
||||||
|
import { Syncable, ArgPair } from '../src/sync';
|
||||||
|
|
||||||
|
import { MockKeyStore, MockSigner } from './mock';
|
||||||
|
|
||||||
|
|
||||||
|
describe('sync', async () => {
|
||||||
|
it('sync_merge', () => {
|
||||||
|
const mockSigner = new MockSigner();
|
||||||
|
const s = new Syncable('foo', {
|
||||||
|
bar: 'baz',
|
||||||
|
});
|
||||||
|
s.setSigner(mockSigner);
|
||||||
|
const changePair = new ArgPair('xyzzy', 42);
|
||||||
|
s.update([changePair], 'ch-ch-cha-changes');
|
||||||
|
assert.equal(s.m.data['xyzzy'], 42)
|
||||||
|
assert.equal(s.m.data['bar'], 'baz')
|
||||||
|
assert.equal(s.m['id'], 'foo')
|
||||||
|
assert.equal(Automerge.getHistory(s.m).length, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sync_serialize', () => {
|
||||||
|
const mockSigner = new MockSigner();
|
||||||
|
const s = new Syncable('foo', {
|
||||||
|
bar: 'baz',
|
||||||
|
});
|
||||||
|
s.setSigner(mockSigner);
|
||||||
|
const j = s.toJSON();
|
||||||
|
const ss = Syncable.fromJSON(j);
|
||||||
|
assert.equal(ss.m['id'], 'foo');
|
||||||
|
assert.equal(ss.m['data']['bar'], 'baz');
|
||||||
|
assert.equal(Automerge.getHistory(ss.m).length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sync_sign_and_wrap', () => {
|
||||||
|
const mockSigner = new MockSigner();
|
||||||
|
const s = new Syncable('foo', {
|
||||||
|
bar: 'baz',
|
||||||
|
});
|
||||||
|
s.setSigner(mockSigner);
|
||||||
|
s.onwrap = (e) => {
|
||||||
|
const j = e.toJSON();
|
||||||
|
const v = JSON.parse(j);
|
||||||
|
assert.deepEqual(v.payload, e.o.payload);
|
||||||
|
|
||||||
|
}
|
||||||
|
s.sign();
|
||||||
|
});
|
||||||
|
it('sync_verify_success', async () => {
|
||||||
|
const pksa = fs.readFileSync(__dirname + '/privatekeys.asc');
|
||||||
|
const pks = await pgp.key.readArmored(pksa);
|
||||||
|
await pks.keys[0].decrypt('merman');
|
||||||
|
await pks.keys[1].decrypt('beastman');
|
||||||
|
|
||||||
|
const pubksa = fs.readFileSync(__dirname + '/publickeys.asc');
|
||||||
|
const pubks = await pgp.key.readArmored(pubksa);
|
||||||
|
|
||||||
|
const oneStore = new MockKeyStore(pks.keys[0], pubks.keys);
|
||||||
|
const twoStore = new MockKeyStore(pks.keys[1], pubks.keys);
|
||||||
|
const threeStore = new MockKeyStore(pks.keys[2], [pubks.keys[0], pubks.keys[2]]);
|
||||||
|
|
||||||
|
const oneSigner = new PGPSigner(oneStore);
|
||||||
|
const twoSigner = new PGPSigner(twoStore);
|
||||||
|
const threeSigner = new PGPSigner(threeStore);
|
||||||
|
|
||||||
|
const x = new Syncable('foo', {
|
||||||
|
bar: 'baz',
|
||||||
|
});
|
||||||
|
x.setSigner(oneSigner);
|
||||||
|
|
||||||
|
// TODO: make this look better
|
||||||
|
x.onwrap = (e) => {
|
||||||
|
let updateData = new ArgPair('bar', 'xyzzy');
|
||||||
|
x.update([updateData], 'change one');
|
||||||
|
|
||||||
|
x.onwrap = (e) => {
|
||||||
|
x.setSigner(twoSigner);
|
||||||
|
updateData = new ArgPair('bar', 42);
|
||||||
|
x.update([updateData], 'change two');
|
||||||
|
|
||||||
|
x.onwrap = (e) => {
|
||||||
|
const p = e.unwrap();
|
||||||
|
p.setSigner(twoSigner);
|
||||||
|
p.onauthenticate = (v) => {
|
||||||
|
assert(v);
|
||||||
|
}
|
||||||
|
p.authenticate();
|
||||||
|
}
|
||||||
|
|
||||||
|
x.sign();
|
||||||
|
};
|
||||||
|
|
||||||
|
x.sign();
|
||||||
|
}
|
||||||
|
|
||||||
|
x.sign();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sync_verify_fail', async () => {
|
||||||
|
const pksa = fs.readFileSync(__dirname + '/privatekeys.asc');
|
||||||
|
const pks = await pgp.key.readArmored(pksa);
|
||||||
|
await pks.keys[0].decrypt('merman');
|
||||||
|
await pks.keys[1].decrypt('beastman');
|
||||||
|
|
||||||
|
const pubksa = fs.readFileSync(__dirname + '/publickeys.asc');
|
||||||
|
const pubks = await pgp.key.readArmored(pubksa);
|
||||||
|
|
||||||
|
const oneStore = new MockKeyStore(pks.keys[0], pubks.keys);
|
||||||
|
const twoStore = new MockKeyStore(pks.keys[1], pubks.keys);
|
||||||
|
const threeStore = new MockKeyStore(pks.keys[2], [pubks.keys[0], pubks.keys[2]]);
|
||||||
|
|
||||||
|
const oneSigner = new PGPSigner(oneStore);
|
||||||
|
const twoSigner = new PGPSigner(twoStore);
|
||||||
|
const threeSigner = new PGPSigner(threeStore);
|
||||||
|
|
||||||
|
const x = new Syncable('foo', {
|
||||||
|
bar: 'baz',
|
||||||
|
});
|
||||||
|
x.setSigner(oneSigner);
|
||||||
|
|
||||||
|
// TODO: make this look better
|
||||||
|
x.onwrap = (e) => {
|
||||||
|
let updateData = new ArgPair('bar', 'xyzzy');
|
||||||
|
x.update([updateData], 'change one');
|
||||||
|
|
||||||
|
x.onwrap = (e) => {
|
||||||
|
x.setSigner(twoSigner);
|
||||||
|
updateData = new ArgPair('bar', 42);
|
||||||
|
x.update([updateData], 'change two');
|
||||||
|
|
||||||
|
x.onwrap = (e) => {
|
||||||
|
const p = e.unwrap();
|
||||||
|
p.setSigner(threeSigner);
|
||||||
|
p.onauthenticate = (v) => {
|
||||||
|
assert(!v);
|
||||||
|
}
|
||||||
|
p.authenticate();
|
||||||
|
}
|
||||||
|
|
||||||
|
x.sign();
|
||||||
|
};
|
||||||
|
|
||||||
|
x.sign();
|
||||||
|
}
|
||||||
|
|
||||||
|
x.sign();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
xit('sync_verify_shallow_tricked', async () => {
|
||||||
|
const pksa = fs.readFileSync(__dirname + '/privatekeys.asc');
|
||||||
|
const pks = await pgp.key.readArmored(pksa);
|
||||||
|
await pks.keys[0].decrypt('merman');
|
||||||
|
await pks.keys[1].decrypt('beastman');
|
||||||
|
|
||||||
|
const pubksa = fs.readFileSync(__dirname + '/publickeys.asc');
|
||||||
|
const pubks = await pgp.key.readArmored(pubksa);
|
||||||
|
|
||||||
|
const oneStore = new MockKeyStore(pks.keys[0], pubks.keys);
|
||||||
|
const twoStore = new MockKeyStore(pks.keys[1], pubks.keys);
|
||||||
|
const threeStore = new MockKeyStore(pks.keys[2], [pubks.keys[0], pubks.keys[2]]);
|
||||||
|
|
||||||
|
const oneSigner = new PGPSigner(oneStore);
|
||||||
|
const twoSigner = new PGPSigner(twoStore);
|
||||||
|
const threeSigner = new PGPSigner(threeStore);
|
||||||
|
|
||||||
|
const x = new Syncable('foo', {
|
||||||
|
bar: 'baz',
|
||||||
|
});
|
||||||
|
x.setSigner(twoSigner);
|
||||||
|
|
||||||
|
// TODO: make this look better
|
||||||
|
x.onwrap = (e) => {
|
||||||
|
let updateData = new ArgPair('bar', 'xyzzy');
|
||||||
|
x.update([updateData], 'change one');
|
||||||
|
|
||||||
|
x.onwrap = (e) => {
|
||||||
|
updateData = new ArgPair('bar', 42);
|
||||||
|
x.update([updateData], 'change two');
|
||||||
|
x.setSigner(oneSigner);
|
||||||
|
|
||||||
|
x.onwrap = (e) => {
|
||||||
|
const p = e.unwrap();
|
||||||
|
p.setSigner(threeSigner);
|
||||||
|
p.onauthenticate = (v) => {
|
||||||
|
assert(v);
|
||||||
|
p.onauthenticate = (v) => {
|
||||||
|
assert(!v);
|
||||||
|
}
|
||||||
|
p.authenticate(true);
|
||||||
|
}
|
||||||
|
p.authenticate();
|
||||||
|
}
|
||||||
|
|
||||||
|
x.sign();
|
||||||
|
};
|
||||||
|
|
||||||
|
x.sign();
|
||||||
|
}
|
||||||
|
|
||||||
|
x.sign();
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
14
apps/cic-meta/tests/3_transport.ts
Normal file
14
apps/cic-meta/tests/3_transport.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import * as assert from 'assert';
|
||||||
|
|
||||||
|
import { MockPubSub, MockConsumer } from './mock';
|
||||||
|
|
||||||
|
describe('transport', () => {
|
||||||
|
it('pub_sub', () => {
|
||||||
|
const c = new MockConsumer();
|
||||||
|
const ps = new MockPubSub('foo', c);
|
||||||
|
ps.pub('foo');
|
||||||
|
ps.pub('bar');
|
||||||
|
ps.flush();
|
||||||
|
assert.deepEqual(c.omnoms, ['foo', 'bar']);
|
||||||
|
});
|
||||||
|
});
|
||||||
46
apps/cic-meta/tests/4_auth.ts
Normal file
46
apps/cic-meta/tests/4_auth.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import assert = require('assert');
|
||||||
|
import pgp = require('openpgp');
|
||||||
|
import crypto = require('crypto');
|
||||||
|
|
||||||
|
import { Syncable, ArgPair } from '../src/sync';
|
||||||
|
|
||||||
|
import { MockKeyStore, MockSignable } from './mock';
|
||||||
|
|
||||||
|
import { PGPSigner } from '../src/auth';
|
||||||
|
|
||||||
|
|
||||||
|
describe('auth', async () => {
|
||||||
|
await it('digest', async () => {
|
||||||
|
const opts = {
|
||||||
|
userIds: [
|
||||||
|
{
|
||||||
|
name: 'John Marston',
|
||||||
|
email: 'red@dead.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
numBits: 2048,
|
||||||
|
passphrase: 'foo',
|
||||||
|
};
|
||||||
|
const pkgen = await pgp.generateKey(opts);
|
||||||
|
const pka = pkgen.privateKeyArmored;
|
||||||
|
const pks = await pgp.key.readArmored(pka);
|
||||||
|
await pks.keys[0].decrypt('foo');
|
||||||
|
const pubka = pkgen.publicKeyArmored;
|
||||||
|
const pubks = await pgp.key.readArmored(pubka);
|
||||||
|
const keyStore = new MockKeyStore(pks.keys[0], pubks.keys);
|
||||||
|
const s = new PGPSigner(keyStore);
|
||||||
|
|
||||||
|
const message = await pgp.cleartext.fromText('foo');
|
||||||
|
s.onverify = (ok) => {
|
||||||
|
assert(ok);
|
||||||
|
}
|
||||||
|
s.onsign = (signature) => {
|
||||||
|
s.onverify((v) => {
|
||||||
|
console.log('bar', v);
|
||||||
|
});
|
||||||
|
s.verify('foo', signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
await s.sign('foo');
|
||||||
|
});
|
||||||
|
});
|
||||||
47
apps/cic-meta/tests/999_functional.ts
Normal file
47
apps/cic-meta/tests/999_functional.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import * as assert from 'assert';
|
||||||
|
import * as pgp from 'openpgp';
|
||||||
|
|
||||||
|
import { Dispatcher } from '../src/dispatch';
|
||||||
|
import { User } from '../src/assets/user';
|
||||||
|
import { PGPSigner, KeyStore } from '../src/auth';
|
||||||
|
import { SubConsumer } from '../src/transport';
|
||||||
|
|
||||||
|
import { MockStore, MockPubSub, MockConsumer, MockKeyStore } from './mock';
|
||||||
|
|
||||||
|
async function createKeyStore() {
|
||||||
|
const opts = {
|
||||||
|
userIds: [
|
||||||
|
{
|
||||||
|
name: 'John Marston',
|
||||||
|
email: 'red@dead.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
numBits: 2048,
|
||||||
|
passphrase: 'foo',
|
||||||
|
};
|
||||||
|
const pkgen = await pgp.generateKey(opts);
|
||||||
|
const pka = pkgen.privateKeyArmored;
|
||||||
|
const pks = await pgp.key.readArmored(pka);
|
||||||
|
await pks.keys[0].decrypt('foo');
|
||||||
|
return new MockKeyStore(pks.keys[0], []);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('fullchain', async () => {
|
||||||
|
it('dispatch_and_publish_user', async () => {
|
||||||
|
const g = await createKeyStore();
|
||||||
|
const n = new PGPSigner(g);
|
||||||
|
const u = new User('u1', {});
|
||||||
|
u.setSigner(n);
|
||||||
|
u.setName('Nico', 'Bellic');
|
||||||
|
const s = new MockStore('fooStore');
|
||||||
|
const c = new MockConsumer();
|
||||||
|
const p = new MockPubSub('fooPubSub', c);
|
||||||
|
const d = new Dispatcher(s, p);
|
||||||
|
u.onwrap = (e) => {
|
||||||
|
d.add(u.id, e);
|
||||||
|
d.sync(0);
|
||||||
|
assert.equal(p.pubs.length, 1);
|
||||||
|
};
|
||||||
|
u.sign();
|
||||||
|
});
|
||||||
|
});
|
||||||
150
apps/cic-meta/tests/mock.ts
Normal file
150
apps/cic-meta/tests/mock.ts
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import * as crypto from 'crypto';
|
||||||
|
|
||||||
|
import { Signable, Signature, KeyStore } from '../src/auth';
|
||||||
|
import { Store } from '../src/store';
|
||||||
|
import { PubSub, SubConsumer } from '../src/transport';
|
||||||
|
import { Syncable } from '../src/sync';
|
||||||
|
|
||||||
|
class MockStore implements Store {
|
||||||
|
|
||||||
|
contents: Object
|
||||||
|
name: string
|
||||||
|
|
||||||
|
constructor(name:string) {
|
||||||
|
this.name = name;
|
||||||
|
this.contents = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public put(k:string, v:Syncable, existsOk = false) {
|
||||||
|
if (!existsOk && this.contents[k] !== undefined) {
|
||||||
|
throw '"' + k + '" already exists in store ' + this.name;
|
||||||
|
}
|
||||||
|
this.contents[k] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(k:string): Syncable {
|
||||||
|
return this.contents[k];
|
||||||
|
}
|
||||||
|
|
||||||
|
public delete(k:string) {
|
||||||
|
delete this.contents[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockSigner {
|
||||||
|
onsign: (string) => void
|
||||||
|
onverify: (boolean) => void
|
||||||
|
public verify(src:string, signature:Signature) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sign(s:string):boolean {
|
||||||
|
this.onsign('there would be a signature here');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public prepare(m:Signable):boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public fingerprint():string {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockConsumer implements SubConsumer {
|
||||||
|
|
||||||
|
omnoms: Array<string>
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.omnoms = Array<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public post(v:string) {
|
||||||
|
this.omnoms.push(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockPubSub implements PubSub {
|
||||||
|
|
||||||
|
pubs: Array<string>
|
||||||
|
consumer: SubConsumer
|
||||||
|
|
||||||
|
constructor(name:string, consumer:SubConsumer) {
|
||||||
|
this.pubs = Array<string>();
|
||||||
|
this.consumer = consumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public pub(v:string): boolean {
|
||||||
|
this.pubs.push(v);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public flush() {
|
||||||
|
while (this.pubs.length > 0) {
|
||||||
|
const s = this.pubs.shift();
|
||||||
|
this.consumer.post(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public close() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockSignable implements Signable {
|
||||||
|
|
||||||
|
src: string
|
||||||
|
dst: string
|
||||||
|
|
||||||
|
constructor(src:string) {
|
||||||
|
this.src = src;
|
||||||
|
}
|
||||||
|
|
||||||
|
public digest():string {
|
||||||
|
const h = crypto.createHash('sha256');
|
||||||
|
h.update(this.src);
|
||||||
|
this.dst= h.digest('hex');
|
||||||
|
return this.dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockKeyStore implements KeyStore {
|
||||||
|
|
||||||
|
pk: any
|
||||||
|
pubks: Array<any>
|
||||||
|
|
||||||
|
constructor(pk:any, pubks:Array<any>) {
|
||||||
|
this.pk = pk;
|
||||||
|
this.pubks = pubks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPrivateKey(): any {
|
||||||
|
return this.pk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTrustedKeys(): Array<any> {
|
||||||
|
return this.pubks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTrustedActiveKeys(): Array<any> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getEncryptKeys(): Array<any> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFingerprint(): string {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
MockStore,
|
||||||
|
MockPubSub,
|
||||||
|
MockConsumer,
|
||||||
|
MockSignable,
|
||||||
|
MockKeyStore,
|
||||||
|
MockSigner,
|
||||||
|
};
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
|
import Automerge = require('automerge');
|
||||||
import assert = require('assert');
|
import assert = require('assert');
|
||||||
import fs = require('fs');
|
import fs = require('fs');
|
||||||
import pgp = require('openpgp');
|
import pgp = require('openpgp');
|
||||||
import sqlite = require('sqlite3');
|
import sqlite = require('sqlite3');
|
||||||
|
|
||||||
import * as handlers from '../scripts/server/handlers';
|
import * as handlers from '../scripts/server/handlers';
|
||||||
import { Envelope, Syncable, ArgPair, PGPKeyStore, PGPSigner, KeyStore, Signer, SqliteAdapter } from 'crdt-meta';
|
import { Envelope, Syncable, ArgPair } from '../src/sync';
|
||||||
|
import { PGPKeyStore, PGPSigner, KeyStore, Signer } from '../src/auth';
|
||||||
|
import { SqliteAdapter } from '../src/db';
|
||||||
|
|
||||||
function createKeystore() {
|
function createKeystore() {
|
||||||
const pksa = fs.readFileSync(__dirname + '/privatekeys.asc', 'utf-8');
|
const pksa = fs.readFileSync(__dirname + '/privatekeys.asc', 'utf-8');
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ def process_exit_insufficient_balance(display_key: str, user: Account, ussd_sess
|
|||||||
operational_balance = get_cached_operational_balance(blockchain_address=user.blockchain_address)
|
operational_balance = get_cached_operational_balance(blockchain_address=user.blockchain_address)
|
||||||
|
|
||||||
# compile response data
|
# compile response data
|
||||||
user_input = ussd_session.get('user_input').split('*')[-1]
|
user_input = ussd_session.get('session_data').get('transaction_amount')
|
||||||
transaction_amount = to_wei(value=int(user_input))
|
transaction_amount = to_wei(value=int(user_input))
|
||||||
token_symbol = 'SRF'
|
token_symbol = 'SRF'
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ def process_exit_insufficient_balance(display_key: str, user: Account, ussd_sess
|
|||||||
amount=from_wei(transaction_amount),
|
amount=from_wei(transaction_amount),
|
||||||
token_symbol=token_symbol,
|
token_symbol=token_symbol,
|
||||||
recipient_information=tx_recipient_information,
|
recipient_information=tx_recipient_information,
|
||||||
token_balance=operational_balance
|
token_balance=operational_balance,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -401,7 +401,11 @@ def process_request(user_input: str, user: Account, ussd_session: Optional[dict]
|
|||||||
'exit_invalid_pin',
|
'exit_invalid_pin',
|
||||||
'exit_invalid_new_pin',
|
'exit_invalid_new_pin',
|
||||||
'exit_pin_mismatch',
|
'exit_pin_mismatch',
|
||||||
'exit_invalid_request'
|
'exit_invalid_request',
|
||||||
|
"exit_insufficient_balance",
|
||||||
|
"exit_successful_transaction",
|
||||||
|
"help",
|
||||||
|
"complete"
|
||||||
] and person_metadata is not None:
|
] and person_metadata is not None:
|
||||||
return UssdMenu.find_by_name(name='start')
|
return UssdMenu.find_by_name(name='start')
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -45,7 +45,15 @@ def is_authorized_pin(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
|||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
user_input, ussd_session, user = state_machine_data
|
user_input, ussd_session, user = state_machine_data
|
||||||
return user.verify_password(password=user_input)
|
pin_validity = user.verify_password(password=user_input)
|
||||||
|
if pin_validity is True:
|
||||||
|
return user.verify_password(password=user_input)
|
||||||
|
else:
|
||||||
|
# bump number for failed attempts
|
||||||
|
user.failed_pin_attempts += 1
|
||||||
|
Account.session.add(user)
|
||||||
|
Account.session.commit()
|
||||||
|
return pin_validity
|
||||||
|
|
||||||
|
|
||||||
def is_locked_account(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
def is_locked_account(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ WORKDIR /usr/src
|
|||||||
ARG pip_extra_index_url_flag='--extra-index-url https://pip.grassrootseconomics.net:8433'
|
ARG pip_extra_index_url_flag='--extra-index-url https://pip.grassrootseconomics.net:8433'
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt install -y gcc gnupg libpq-dev wget make g++ gnupg bash procps git
|
apt install -y gcc gnupg libpq-dev wget make g++ gnupg bash procps git python-pycurl libcurl4-openssl-dev libssl-dev
|
||||||
|
|
||||||
# create secrets directory
|
# create secrets directory
|
||||||
RUN mkdir -vp pgp/keys
|
RUN mkdir -vp pgp/keys
|
||||||
|
|||||||
281
apps/cic-ussd/expect/account_tests.yml
Normal file
281
apps/cic-ussd/expect/account_tests.yml
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
- config:
|
||||||
|
- testset: "account tests"
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "create account [EN]"
|
||||||
|
- url: "/"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek020", "text": ""}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"175"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"contains", expected: "END Your account is being created. You will receive an SMS when your account is ready.\nAkaunti yako ya Sarafu inatayarishwa. Utapokea ujumbe wa SMS akaunti yako ikiwa tayari.\n" }
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "create account [SW]"
|
||||||
|
- url: "/"
|
||||||
|
- method: "POST"
|
||||||
|
- delay: 2
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek021", "text": ""}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"175"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"contains", expected: "END Your account is being created. You will receive an SMS when your account is ready.\nAkaunti yako ya Sarafu inatayarishwa. Utapokea ujumbe wa SMS akaunti yako ikiwa tayari.\n" }
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "select preferred language prompt [EN]"
|
||||||
|
- url: "/"
|
||||||
|
- method: "POST"
|
||||||
|
- delay: 5 # delay to allow celery tasks to run to create account
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": ""}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"53"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Welcome to Sarafu\n1. English\n2. Kiswahili\n3. Help"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "select preferred language prompt [SW]"
|
||||||
|
- url: "/"
|
||||||
|
- method: "POST"
|
||||||
|
- delay: 5 # delay to allow celery tasks to run to create account
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": ""}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"53"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Welcome to Sarafu\n1. English\n2. Kiswahili\n3. Help"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "pin entry [EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 2 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"54"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Please enter a PIN to manage your account.\n0. Back"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "pin entry [SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 2 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"59"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Tafadhali weka PIN ili kudhibiti akaunti yako.\n0. Nyuma"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "pin entry confirmation [EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 2 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1*0000"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"32"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Enter your PIN again\n0. Back"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "pin entry confirmation [SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 2 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2*1111"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"31"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka PIN yako tena\n0. Nyuma"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "given names entry[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 3 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1*0000*0000"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"28"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Enter first name\n0. Back"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "given names entry[SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 3 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2*1111*1111"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"37"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka jina lako la kwanza\n0. Nyuma"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "family name entry[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 2 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1*0000*0000*Kimani"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"27"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Enter last name\n0. Back"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "family name entry[SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 2 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2*1111*1111*Chebet"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"37"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka jina lako la mwisho\n0. Nyuma"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "gender selection[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 2 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1*0000*0000*Kimani*Omollo"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"42"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Enter gender\n1. Male\n2. Female\n0. Back"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "gender selection[SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 2 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2*1111*1111*Chebet*Musau"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"53"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka jinsia yako\n1. Mwanaume\n2. Mwanamke\n0. Nyuma"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "location entry[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 2 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1*0000*0000*Kimani*Omollo*1"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"26"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Enter location\n0. Back"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "location entry[SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 2 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2*1111*1111*Chebet*Musau*2"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"27"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka eneo lako\n0. Nyuma"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "product entry[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 2 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1*0000*0000*Kimani*Omollo*1*Kangemi"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"55"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Please enter a product or service you offer\n0. Back"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "product entry[SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 2 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2*1111*1111*Chebet*Musau*2*Chebarbar"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"52"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Tafadhali weka bidhaa ama huduma unauza\n0. Nyuma"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "start menu[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 2 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek022", "text": "1*0000*0000*Kimani*Omollo*1*Kangemi*Potatoes"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"51"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Balance 50.00 SRF\n1. Send\n2. My Account\n3. Help"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "accounts"
|
||||||
|
- name: "start menu[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: 2 # delay
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek023", "text": "2*1111*1111*Chebet*Musau*2*Musau*Mandazi"}'
|
||||||
|
- headers: {'Content-Type': 'application/json'}
|
||||||
|
- expected_status: [200]
|
||||||
|
- validators:
|
||||||
|
- compare: {"header": "content-type", "comparator": "str_eq", "expected":"text/plain"}
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"56"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Salio 50.00 SRF\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"}
|
||||||
9
apps/cic-ussd/expect/run_smoke_tests.sh
Normal file
9
apps/cic-ussd/expect/run_smoke_tests.sh
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ -z "$TEST_SERVER_URL" ];
|
||||||
|
then
|
||||||
|
echo "The test server url is not set !"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
pyresttest "$TEST_SERVER_URL" ./test_suite.yml --log debug
|
||||||
2
apps/cic-ussd/expect/test_suite.yml
Normal file
2
apps/cic-ussd/expect/test_suite.yml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
- import: account_tests.yml
|
||||||
|
- import: transaction_tests.yml
|
||||||
285
apps/cic-ussd/expect/transaction_tests.yml
Normal file
285
apps/cic-ussd/expect/transaction_tests.yml
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
- config:
|
||||||
|
- testset: "transaction tests"
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "send tokens[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "3"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek024", "text": ""}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "51" }
|
||||||
|
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Balance 50.00 SRF\n1. Send\n2. My Account\n3. Help" }
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "send tokens[SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "3"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek025", "text": ""}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"56"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Salio 50.00 SRF\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "enter recipients phone number[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek024", "text": "1"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "30" }
|
||||||
|
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Enter phone number\n0. Back" }
|
||||||
|
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "enter recipients phone number[SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek025", "text": "1"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"33"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka nambari ya simu\n0. Nyuma"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "enter token amount[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek024", "text": "1*0712345679"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "24" }
|
||||||
|
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Enter amount\n0. Back" }
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "enter token amount[SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek025", "text": "1*0712345678"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"25"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka kiwango\n0. Nyuma"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "transaction pin authorization[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek024", "text": "1*0712345679*15"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "129" }
|
||||||
|
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Chebet Musau +254712345679 will receive 15.00 SRF from Kimani Omollo +254712345678.\nPlease enter your PIN to confirm.\n0. Back" }
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "transaction pin authorization[SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek025", "text": "1*0712345678*18"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"148"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Kimani Omollo +254712345678 atapokea 18.00 SRF kutoka kwa Chebet Musau +254712345679.\nTafadhali weka nambari yako ya siri kudhibitisha.\n0. Nyuma"}
|
||||||
|
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "transaction pin authorization-invalid-pin[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek024", "text": "1*0712345679*15*6987"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "65" }
|
||||||
|
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Please enter your PIN. You have 2 attempts remaining.\n0. Back" }
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "transaction pin authorization-invalid-pin[SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek025", "text": "1*0712345678*18*7845"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"62"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka nambari ya siri. Una majaribio 2 yaliyobaki.\n0. Nyuma"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "transaction pin authorization-valid-pin[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek024", "text": "1*0712345679*15*6987*0000"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "133" }
|
||||||
|
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Your request has been sent. Chebet Musau +254712345679 will receive 15.00 SRF from Kimani Omollo +254712345678.\n00. Back\n99. Exit" }
|
||||||
|
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "transaction pin authorization-valid-pin[SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek025", "text": "1*0712345678*18*7845*1111"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"131"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Ombi lako limetumwa. Kimani Omollo +254712345678 atapokea 18.00 SRF kutoka kwa Chebet Musau +254712345679.\n00. Nyuma\n99. Ondoka"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "send tokens-2[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "3"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek026", "text": ""}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "51" }
|
||||||
|
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Balance 53.00 SRF\n1. Send\n2. My Account\n3. Help" }
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "send tokens-2[SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "3"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek027", "text": ""}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"56"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Salio 47.00 SRF\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "enter recipients phone number-2[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek026", "text": "1"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "30" }
|
||||||
|
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Enter phone number\n0. Back" }
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "enter recipients phone number-2[SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek027", "text": "1"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"33"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka nambari ya simu\n0. Nyuma"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "enter token amount-2[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek026", "text": "1*0712345679"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "24" }
|
||||||
|
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Enter amount\n0. Back" }
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "enter token amount-2[SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek027", "text": "1*0712345678"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"25"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Weka kiwango\n0. Nyuma"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "transaction pin authorization-2[EN]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345678", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek026", "text": "1*0712345679*850"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: { "header": "content-length", "comparator": "str_eq", "expected": "156" }
|
||||||
|
- compare: { "raw_body": "", "comparator": "str_eq", expected: "CON Payment of 850.00 SRF to Chebet Musau +254712345679 has failed due to insufficient balance.\nYour Sarafu-Network balances is: 53.00 SRF\n00. Back\n99. Exit"}
|
||||||
|
|
||||||
|
- test:
|
||||||
|
- group: "transactions"
|
||||||
|
- name: "transaction pin authorization-2[SW]"
|
||||||
|
- url: "/"
|
||||||
|
- delay: "2"
|
||||||
|
- method: "POST"
|
||||||
|
- body: '{"serviceCode": "*483*46#", "phoneNumber": "+254712345679", "sessionId": "AT_Idjhfuvelw64ffbweiy73nd5vnek027", "text": "1*0712345678*1800"}'
|
||||||
|
- headers: { 'Content-Type': 'application/json' }
|
||||||
|
- expected_status: [ 200 ]
|
||||||
|
- validators:
|
||||||
|
- compare: { "header": "content-type", "comparator": "str_eq", "expected": "text/plain" }
|
||||||
|
- compare: {"header": "content-length", "comparator": "str_eq", "expected":"186"}
|
||||||
|
- compare: {"raw_body":"", "comparator":"str_eq", expected: "CON Malipo ya 1800.00 SRF kwa Kimani Omollo +254712345678 halijakamilika kwa sababu salio lako haitoshi.\nAkaunti yako ya Sarafu-Network ina salio ifuatayo: 47.00 SRF\n00. Nyuma\n99. Ondoka"}
|
||||||
@@ -2,3 +2,4 @@ cic_base[full_graph]~=0.1.2a68
|
|||||||
cic-eth~=0.11.0b3
|
cic-eth~=0.11.0b3
|
||||||
cic-notify~=0.4.0a4
|
cic-notify~=0.4.0a4
|
||||||
cic-types~=0.1.0a10
|
cic-types~=0.1.0a10
|
||||||
|
pyresttest==1.7.1
|
||||||
@@ -154,8 +154,8 @@ en:
|
|||||||
00. Back
|
00. Back
|
||||||
99. Exit
|
99. Exit
|
||||||
exit_insufficient_balance: |-
|
exit_insufficient_balance: |-
|
||||||
CON Payment of %{amount} %{token_symbol} to %{recipient_information} has failed due to insufficent balance.
|
CON Payment of %{amount} %{token_symbol} to %{recipient_information} has failed due to insufficient balance.
|
||||||
Your Sarafu-Network balances is: %{token_balance}
|
Your Sarafu-Network balances is: %{token_balance} %{token_symbol}
|
||||||
00. Back
|
00. Back
|
||||||
99. Exit
|
99. Exit
|
||||||
help: |-
|
help: |-
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ sw:
|
|||||||
99. Ondoka
|
99. Ondoka
|
||||||
exit_insufficient_balance: |-
|
exit_insufficient_balance: |-
|
||||||
CON Malipo ya %{amount} %{token_symbol} kwa %{recipient_information} halijakamilika kwa sababu salio lako haitoshi.
|
CON Malipo ya %{amount} %{token_symbol} kwa %{recipient_information} halijakamilika kwa sababu salio lako haitoshi.
|
||||||
Akaunti yako ya Sarafu-Network ina salio ifuatayo: %{token_balance}
|
Akaunti yako ya Sarafu-Network ina salio ifuatayo: %{token_balance} %{token_symbol}
|
||||||
00. Nyuma
|
00. Nyuma
|
||||||
99. Ondoka
|
99. Ondoka
|
||||||
help: |-
|
help: |-
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ const path = require('path');
|
|||||||
const http = require('http');
|
const http = require('http');
|
||||||
|
|
||||||
const cic = require('cic-client-meta');
|
const cic = require('cic-client-meta');
|
||||||
const crdt = require('crdt-meta');
|
|
||||||
|
|
||||||
//const conf = JSON.parse(fs.readFileSync('./cic.conf'));
|
//const conf = JSON.parse(fs.readFileSync('./cic.conf'));
|
||||||
|
|
||||||
const config = new crdt.Config('./config');
|
const config = new cic.Config('./config');
|
||||||
config.process();
|
config.process();
|
||||||
console.log(config);
|
console.log(config);
|
||||||
|
|
||||||
@@ -42,7 +41,7 @@ function sendit(uid, envelope) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function doOne(keystore, filePath) {
|
function doOne(keystore, filePath) {
|
||||||
const signer = new crdt.PGPSigner(keystore);
|
const signer = new cic.PGPSigner(keystore);
|
||||||
const parts = path.basename(filePath).split('.');
|
const parts = path.basename(filePath).split('.');
|
||||||
const ethereum_address = path.basename(parts[0]);
|
const ethereum_address = path.basename(parts[0]);
|
||||||
|
|
||||||
@@ -52,7 +51,7 @@ function doOne(keystore, filePath) {
|
|||||||
//console.log(o);
|
//console.log(o);
|
||||||
fs.unlinkSync(filePath);
|
fs.unlinkSync(filePath);
|
||||||
|
|
||||||
const s = new crdt.Syncable(uid, o);
|
const s = new cic.Syncable(uid, o);
|
||||||
s.setSigner(signer);
|
s.setSigner(signer);
|
||||||
s.onwrap = (env) => {
|
s.onwrap = (env) => {
|
||||||
sendit(uid, env);
|
sendit(uid, env);
|
||||||
@@ -66,7 +65,7 @@ const publicKeyPath = path.join(config.get('PGP_EXPORTS_DIR'), config.get('PGP_P
|
|||||||
pk = fs.readFileSync(privateKeyPath);
|
pk = fs.readFileSync(privateKeyPath);
|
||||||
pubk = fs.readFileSync(publicKeyPath);
|
pubk = fs.readFileSync(publicKeyPath);
|
||||||
|
|
||||||
new crdt.PGPKeyStore(
|
new cic.PGPKeyStore(
|
||||||
config.get('PGP_PASSPHRASE'),
|
config.get('PGP_PASSPHRASE'),
|
||||||
pk,
|
pk,
|
||||||
pubk,
|
pubk,
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ const http = require('http');
|
|||||||
|
|
||||||
const cic = require('cic-client-meta');
|
const cic = require('cic-client-meta');
|
||||||
const vcfp = require('vcard-parser');
|
const vcfp = require('vcard-parser');
|
||||||
const crdt = require('crdt-meta');
|
|
||||||
|
|
||||||
//const conf = JSON.parse(fs.readFileSync('./cic.conf'));
|
//const conf = JSON.parse(fs.readFileSync('./cic.conf'));
|
||||||
|
|
||||||
const config = new crdt.Config('./config');
|
const config = new cic.Config('./config');
|
||||||
config.process();
|
config.process();
|
||||||
console.log(config);
|
console.log(config);
|
||||||
|
|
||||||
@@ -43,7 +42,7 @@ function sendit(uid, envelope) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function doOne(keystore, filePath, address) {
|
function doOne(keystore, filePath, address) {
|
||||||
const signer = new crdt.PGPSigner(keystore);
|
const signer = new cic.PGPSigner(keystore);
|
||||||
|
|
||||||
const j = JSON.parse(fs.readFileSync(filePath).toString());
|
const j = JSON.parse(fs.readFileSync(filePath).toString());
|
||||||
const b = Buffer.from(j['vcard'], 'base64');
|
const b = Buffer.from(j['vcard'], 'base64');
|
||||||
@@ -52,8 +51,9 @@ function doOne(keystore, filePath, address) {
|
|||||||
const phone = o.tel[0].value;
|
const phone = o.tel[0].value;
|
||||||
|
|
||||||
cic.Phone.toKey(phone).then((uid) => {
|
cic.Phone.toKey(phone).then((uid) => {
|
||||||
|
const o = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
|
||||||
const s = new crdt.Syncable(uid, address);
|
const s = new cic.Syncable(uid, o);
|
||||||
s.setSigner(signer);
|
s.setSigner(signer);
|
||||||
s.onwrap = (env) => {
|
s.onwrap = (env) => {
|
||||||
sendit(uid, env);
|
sendit(uid, env);
|
||||||
@@ -67,7 +67,7 @@ const publicKeyPath = path.join(config.get('PGP_EXPORTS_DIR'), config.get('PGP_P
|
|||||||
pk = fs.readFileSync(privateKeyPath);
|
pk = fs.readFileSync(privateKeyPath);
|
||||||
pubk = fs.readFileSync(publicKeyPath);
|
pubk = fs.readFileSync(publicKeyPath);
|
||||||
|
|
||||||
new crdt.PGPKeyStore(
|
new cic.PGPKeyStore(
|
||||||
config.get('PGP_PASSPHRASE'),
|
config.get('PGP_PASSPHRASE'),
|
||||||
pk,
|
pk,
|
||||||
pubk,
|
pubk,
|
||||||
@@ -123,7 +123,7 @@ function importMetaPhone(keystore) {
|
|||||||
if (batchCount == batchSize) {
|
if (batchCount == batchSize) {
|
||||||
console.debug('reached batch size, breathing');
|
console.debug('reached batch size, breathing');
|
||||||
batchCount=0;
|
batchCount=0;
|
||||||
setTimeout(importMetaPhone, batchDelay, keystore);
|
setTimeout(importMeta, batchDelay, keystore);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
apps/contract-migration/scripts/package-lock.json
generated
27
apps/contract-migration/scripts/package-lock.json
generated
@@ -29,9 +29,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "14.14.39",
|
"version": "14.14.41",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.39.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz",
|
||||||
"integrity": "sha512-Qipn7rfTxGEDqZiezH+wxqWYR8vcXq5LRpZrETD19Gs4o8LbklbmqotSUsMU+s5G3PJwMRDfNEYoxrcBwIxOuw=="
|
"integrity": "sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g=="
|
||||||
},
|
},
|
||||||
"@types/pbkdf2": {
|
"@types/pbkdf2": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
@@ -297,15 +297,14 @@
|
|||||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
||||||
},
|
},
|
||||||
"cic-client-meta": {
|
"cic-client-meta": {
|
||||||
"version": "0.0.7-alpha.8",
|
"version": "0.0.7-alpha.6",
|
||||||
"resolved": "https://registry.npmjs.org/cic-client-meta/-/cic-client-meta-0.0.7-alpha.8.tgz",
|
"resolved": "https://registry.npmjs.org/cic-client-meta/-/cic-client-meta-0.0.7-alpha.6.tgz",
|
||||||
"integrity": "sha512-NtU4b4dptG2gsKXIvAv1xCxxxhrr801tb8+Co1O+VLx+wvxFyPRxqa2f2eN5nrSnFnljNsWWpE6K5bJZb1+Rqw==",
|
"integrity": "sha512-oIN1aHkPHfsxJKDV6k4f1kX2tcppw3Q+D1b4BoPh0hYjNKNb7gImBMWnGsy8uiD9W6SNYE4sIXyrtct8mvrhsw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@ethereumjs/tx": "^3.0.0-beta.1",
|
"@ethereumjs/tx": "^3.0.0-beta.1",
|
||||||
"automerge": "^0.14.1",
|
"automerge": "^0.14.1",
|
||||||
"crdt-meta": "0.0.8",
|
|
||||||
"ethereumjs-wallet": "^1.0.1",
|
"ethereumjs-wallet": "^1.0.1",
|
||||||
"ini": "^1.3.8",
|
"ini": "^1.3.5",
|
||||||
"openpgp": "^4.10.8",
|
"openpgp": "^4.10.8",
|
||||||
"pg": "^8.4.2",
|
"pg": "^8.4.2",
|
||||||
"sqlite3": "^5.0.0",
|
"sqlite3": "^5.0.0",
|
||||||
@@ -412,18 +411,6 @@
|
|||||||
"printj": "~1.1.0"
|
"printj": "~1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"crdt-meta": {
|
|
||||||
"version": "0.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/crdt-meta/-/crdt-meta-0.0.8.tgz",
|
|
||||||
"integrity": "sha512-CS0sS0L2QWthz7vmu6vzl3p4kcpJ+IKILBJ4tbgN4A3iNG8wnBeuDIv/z3KFFQjcfuP4QAh6E9LywKUTxtDc3g==",
|
|
||||||
"requires": {
|
|
||||||
"automerge": "^0.14.2",
|
|
||||||
"ini": "^1.3.8",
|
|
||||||
"openpgp": "^4.10.8",
|
|
||||||
"pg": "^8.5.1",
|
|
||||||
"sqlite3": "^5.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"create-hash": {
|
"create-hash": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cic-client-meta": "^0.0.7-alpha.8",
|
"cic-client-meta": "0.0.7-alpha.6",
|
||||||
"crdt-meta": "0.0.8",
|
|
||||||
"vcard-parser": "^1.0.0"
|
"vcard-parser": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,11 +345,12 @@ class Verifier:
|
|||||||
address_recovered = address_recovered.replace('"', '')
|
address_recovered = address_recovered.replace('"', '')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
upper_address_recovered = strip_0x(address_recovered).upper()
|
address = strip_0x(address)
|
||||||
|
address_recovered = strip_0x(address_recovered)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise VerifierError(address_recovered, 'metadata (phone) address {} address recovered {}'.format(address, address_recovered))
|
raise VerifierError(address_recovered, 'metadata (phone) address {} address recovered {}'.format(address, address_recovered))
|
||||||
|
|
||||||
if upper_address != upper_address_recovered:
|
if address != address_recovered:
|
||||||
raise VerifierError(address_recovered, 'metadata (phone)')
|
raise VerifierError(address_recovered, 'metadata (phone)')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user