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 ) = > {
2021-08-09 21:13:05 +02:00
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
} ;