cic-stack/apps/cic-meta/scripts/server/handlers.ts

294 lines
7.3 KiB
TypeScript
Raw Normal View History

2021-02-08 18:31:29 +01:00
import * as Automerge from 'automerge';
import * as pgp from 'openpgp';
2021-10-25 20:51:08 +02:00
import * as crypto from 'crypto';
2021-02-08 18:31:29 +01:00
2021-10-25 20:51:08 +02:00
import { Envelope, Syncable, bytesToHex } from '@cicnet/crdt-meta';
2021-02-08 18:31:29 +01:00
function handleNoMergeGet(db, digest, keystore) {
2021-10-25 20:51:08 +02:00
const sql = "SELECT owner_fingerprint, content, mime_type FROM store WHERE hash = '" + digest + "'";
return new Promise<any>((whohoo, doh) => {
2021-02-08 18:31:29 +01:00
db.query(sql, (e, rs) => {
if (e !== null && e !== undefined) {
doh(e);
return;
} else if (rs.rowCount == 0) {
whohoo(false);
return;
}
2021-10-25 20:51:08 +02:00
const immutable = rs.rows[0]['owner_fingerprint'] == undefined;
let mimeType;
if (immutable) {
if (rs.rows[0]['mime_type'] === undefined) {
mimeType = 'application/octet-stream';
} else {
mimeType = rs.rows[0]['mime_type'];
}
} else {
mimeType = 'application/json';
}
2021-02-08 18:31:29 +01:00
const cipherText = rs.rows[0]['content'];
pgp.message.readArmored(cipherText).then((m) => {
const opts = {
message: m,
privateKeys: [keystore.getPrivateKey()],
2021-10-25 20:51:08 +02:00
format: 'binary',
2021-02-08 18:31:29 +01:00
};
pgp.decrypt(opts).then((plainText) => {
2021-10-25 20:51:08 +02:00
let r;
if (immutable) {
r = plainText.data;
} else {
mimeType = 'application/json';
const d = new TextDecoder().decode(plainText.data);
const o = Syncable.fromJSON(d);
r = JSON.stringify(o.m['data']);
}
whohoo([r, mimeType]);
2021-02-08 18:31:29 +01:00
}).catch((e) => {
console.error('decrypt', e);
doh(e);
});
}).catch((e) => {
2021-06-03 15:40:51 +02:00
console.error('message', e);
2021-02-08 18:31:29 +01:00
doh(e);
});
})
});
}
// TODO: add input for change description
function handleServerMergePost(data, db, digest, keystore, signer) {
return new Promise<string>((whohoo, doh) => {
const o = JSON.parse(data);
const cipherText = handleClientMergeGet(db, digest, keystore).then(async (v) => {
let e = undefined;
let s = undefined;
if (v === undefined) {
2021-06-03 15:40:51 +02:00
s = new Syncable(digest, o);
2021-02-08 18:31:29 +01:00
s.onwrap = (e) => {
whohoo(e.toJSON());
};
digest = s.digest();
s.wrap({
digest: digest,
});
} else {
e = Envelope.fromJSON(v);
s = e.unwrap();
2021-10-25 20:51:08 +02:00
console.debug('s', s, o)
2021-02-08 18:31:29 +01:00
s.replace(o, 'server merge');
e.set(s);
s.onwrap = (e) => {
whohoo(e.toJSON());
}
digest = s.digest();
s.wrap({
digest: digest,
});
}
});
});
}
// TODO: this still needs to merge with the stored version
function handleServerMergePut(data, db, digest, keystore, signer) {
return new Promise<boolean>((whohoo, doh) => {
const wrappedData = JSON.parse(data);
if (wrappedData.s === undefined) {
doh('signature missing');
return;
}
const e = Envelope.fromJSON(wrappedData.m);
let s = undefined;
try {
s = e.unwrap();
} catch(e) {
console.error(e)
whohoo(undefined);
}
// TODO: we probably should expose method for replacing the signature, this is too intrusive
s.m = Automerge.change(s.m, 'sign', (doc) => {
doc['signature'] = wrappedData.s;
});
s.setSigner(signer);
s.onauthenticate = (v) => {
console.log('vvv', v);
if (!v) {
whohoo(undefined);
return;
}
const opts = {
message: pgp.message.fromText(s.toJSON()),
publicKeys: keystore.getEncryptKeys(),
};
pgp.encrypt(opts).then((cipherText) => {
const sql = "INSERT INTO store (owner_fingerprint, hash, content) VALUES ('" + signer.fingerprint() + "', '" + digest + "', '" + cipherText.data + "') ON CONFLICT (hash) DO UPDATE SET content = EXCLUDED.content;";
db.query(sql, (e, rs) => {
if (e !== null && e !== undefined) {
doh(e);
return;
}
whohoo(true);
});
});
};
s.authenticate(true)
});
}
function handleClientMergeGet(db, digest, keystore) {
const sql = "SELECT content FROM store WHERE hash = '" + digest + "'";
return new Promise<string>((whohoo, doh) => {
db.query(sql, (e, rs) => {
console.log('rs', e, rs);
if (e !== null && e !== undefined) {
doh(e);
return;
} else if (rs.rowCount == 0) { // TODO fix the postgres/sqlite method name issues, this will now break on postgres
whohoo(undefined);
return;
}
const cipherText = rs.rows[0]['content'];
pgp.message.readArmored(cipherText).then((m) => {
const opts = {
message: m,
privateKeys: [keystore.getPrivateKey()],
};
pgp.decrypt(opts).then((plainText) => {
2021-10-25 20:51:08 +02:00
let d;
if (typeof(plainText.data) == 'string') {
d = plainText.data;
} else {
d = new TextDecoder().decode(plainText.data);
}
const o = Syncable.fromJSON(d);
2021-02-08 18:31:29 +01:00
const e = new Envelope(o);
whohoo(e.toJSON());
}).catch((e) => {
console.error('decrypt', e);
doh(e);
});
}).catch((e) => {
console.error('message', e);
2021-02-08 18:31:29 +01:00
doh(e);
});
});
});
}
// TODO: this still needs to merge with the stored version
function handleClientMergePut(data, db, digest, keystore, signer) {
return new Promise<boolean>((whohoo, doh) => {
let s = undefined;
try {
const e = Envelope.fromJSON(data);
s = e.unwrap();
} catch(e) {
whohoo(false);
console.error(e)
return;
}
s.setSigner(signer);
s.onauthenticate = (v) => {
if (!v) {
whohoo(false);
return;
}
handleClientMergeGet(db, digest, keystore).then((v) => {
if (v !== undefined) {
const env = Envelope.fromJSON(v);
s.merge(env.unwrap());
}
const opts = {
message: pgp.message.fromText(s.toJSON()),
publicKeys: keystore.getEncryptKeys(),
};
pgp.encrypt(opts).then((cipherText) => {
const sql = "INSERT INTO store (owner_fingerprint, hash, content) VALUES ('" + signer.fingerprint() + "', '" + digest + "', '" + cipherText.data + "') ON CONFLICT (hash) DO UPDATE SET content = EXCLUDED.content;";
db.query(sql, (e, rs) => {
if (e !== null && e !== undefined) {
doh(e);
return;
}
whohoo(true);
});
}).catch((e) => {
doh(e);
});
});
};
s.authenticate(true)
});
}
2021-10-25 20:51:08 +02:00
function handleImmutablePost(data, db, digest, keystore, contentType) {
return new Promise<Array<string|boolean>>((whohoo, doh) => {
let data_binary = data;
const h = crypto.createHash('sha256');
h.update(data_binary);
const z = h.digest();
const r = bytesToHex(z);
if (digest) {
if (r != digest) {
doh('hash mismatch: ' + r + ' != ' + digest);
return;
}
} else {
digest = r;
console.debug('calculated digest ' + digest);
}
handleNoMergeGet(db, digest, keystore).then((haveDigest) => {
if (haveDigest !== false) {
whohoo([false, digest]);
return;
}
let message;
if (typeof(data) == 'string') {
data_binary = new TextEncoder().encode(data);
message = pgp.message.fromText(data);
} else {
message = pgp.message.fromBinary(data);
}
const opts = {
message: message,
publicKeys: keystore.getEncryptKeys(),
};
pgp.encrypt(opts).then((cipherText) => {
const sql = "INSERT INTO store (hash, content, mime_type) VALUES ('" + digest + "', '" + cipherText.data + "', '" + contentType + "') ON CONFLICT (hash) DO UPDATE SET content = EXCLUDED.content;";
db.query(sql, (e, rs) => {
if (e !== null && e !== undefined) {
doh(e);
return;
}
whohoo([true, digest]);
});
}).catch((e) => {
doh(e);
});
}).catch((e) => {
doh(e);
});
});
}
2021-02-08 18:31:29 +01:00
export {
handleClientMergePut,
handleClientMergeGet,
handleServerMergePost,
handleServerMergePut,
handleNoMergeGet,
2021-10-25 20:51:08 +02:00
handleImmutablePost,
2021-02-08 18:31:29 +01:00
};