How to modify output in "afterRender" function?



  • Hi Jsreport Team!

    So today's question follow this idea: I need to generate a pdf (that's pretty easy).
    Then, once it is generated, I must digitaly sign it form a script dureing the afterRender call (seems okay so far from what I achieved)

    But now I would like to return it to jsreport so I can send it back to the client/or proceed to further action.

    I did need to use the following so I was able to digitally sign the file but it seems I can't get it back after that:

    const rs = new stream.PassThrough();
    rs.end(new Buffer(res.content));
    
    rs.on('error', (err) => {__log( "error: ", err);});
    
    rs.on('data', chunk => {
        hasher.update(chunk);
    });
    
    rs.on('end', () => {
        const digest = hasher.digest('hex');
        signer.update(digest);
        verifier.update(digest);
        const signature = signer.sign(pvKey, 'base64');
        const verified = verifier.verify(publicKey, signature, 'base64');
        console.log(verified); // return true in my case
    });
    

    any idea about how I should proceed?

    Thanks in advance!!



  • Hi,

    did you try to set the binary buffer back to res.content? That is supported.

    Jan



  • Hi Jan! Thank you for the fast repply!

    How should I proceed exactly? to be honnest I'm quite new with buffers and did look into node js documentation but didn't manage to find a way to do it...
    when I did attempt to do as you said, jsreport throwed me an error saying I was using the wrong output format :/



  • here is one example which does the buffer modification. Would this help?

    https://playground.jsreport.net/studio/workspace/SyuX7ahJe/26



  • tryed it and got back to that error I did encounter last time:

    Error occured - Error during rendering report: First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.
    Stak - TypeError: First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.
        at fromObject (buffer.js:274:9)
        at Function.Buffer.from (buffer.js:106:10)
        at new Buffer (buffer.js:85:17)
        at D:\Work\Project\www\jsreport-server\node_modules\jsreport-scripts\lib\scripts.js:80:24
        at tryCatcher (D:\Work\Project\www\jsreport-server\node_modules\jsreport-scripts\node_modules\bluebird\js\release\util.js:16:23)
        at Promise._settlePromiseFromHandler (D:\Work\Project\www\jsreport-server\node_modules\jsreport-scripts\node_modules\bluebird\js\release\promise.js:512:31)
        at Promise._settlePromise (D:\Work\Project\www\jsreport-server\node_modules\jsreport-scripts\node_modules\bluebird\js\release\promise.js:569:18)
        at Promise._settlePromise0 (D:\Work\Project\www\jsreport-server\node_modules\jsreport-scripts\node_modules\bluebird\js\release\promise.js:614:10)
        at Promise._settlePromises (D:\Work\Project\www\jsreport-server\node_modules\jsreport-scripts\node_modules\bluebird\js\release\promise.js:693:18)
        at Promise._fulfill (D:\Work\Project\www\jsreport-server\node_modules\jsreport-scripts\node_modules\bluebird\js\release\promise.js:638:18)
        at D:\Work\Project\www\jsreport-server\node_modules\jsreport-scripts\node_modules\bluebird\js\release\nodeback.js:42:21
        at ChildProcess.<anonymous> (D:\Work\Project\www\jsreport-server\node_modules\script-manager\lib\manager-processes.js:39:14)
        at emitTwo (events.js:106:13)
        at ChildProcess.emit (events.js:191:7)
        at process.nextTick (internal/child_process.js:752:12)
        at _combinedTickCallback (internal/process/next_tick.js:67:7)
        at process._tickCallback (internal/process/next_tick.js:98:9)
    

    I got the same error even if I return directly the res.content from my function( so setup would be like this:

    function myFun(data){return data;}
    
    res.content = myFun(res.content);
    done();
    

    [Edit:] the following exemple does work
    so the question is: does the signing process alterate the original buffer somehow so it is not "stable" to be used again by jsreport?



  • please share the complete afterRender function, so I can check what you actually set there. The initial code doesn't include a res.content setting.



  • Sure thing,

    the afterRender function is the following:

    function afterRender(req, res, done) {
        var SignFile = require('./extension/sign-file');
        var sf = new SignFile();
        console.log(res);
        var output = sf.setOptions({
            inputBuffer: res.content,
            privateKeyFile: './certs/jds_cons_priv_key.pem',
            publicKeyFile: './certs/jds_cons_pub_key.pem',
        });
      res.content = new Buffer(output, 'binary');
        done();
    }
    

    And this is the SignFile custom extension

    function __log(...arguments) {
        arguments.unshift("\n");
        console.log.apply(null, arguments);
    }
    module.exports = SignFile = (function() {
        
        const crypto = require('crypto');
        const fs = require('fs');
        const path = require('path');
        const stream = require("stream");
    
        const pathname = path.resolve(__dirname, 'temp', 'tmp_.pdf');
        const outputPathname = path.resolve(__dirname, 'temp', 'tmp_signed_.pdf');
    
        const hasher = crypto.createHash('sha256');
    
    
        const signer = crypto.createSign('RSA-SHA256');
        const verifier = crypto.createVerify('RSA-SHA256');
    
        'use strict';
        function SignFile() {
            this.inputBuffer = null;
            this.privateKeyFile = null;
            this.publicKeyFile = null;
    
            this.digest = null;
            this.signature = null;
            this.verified = false;
            this.tempFile = null;
        }
    
        SignFile.prototype.test = function(data) {
            return data.toString('binary');
        }
        SignFile.prototype.setOptions = function(options) {
            var self = this;
            let response = null;
            this.inputBuffer = options.inputBuffer || null;
            this.inputFile = options.inputFile || null;
            this.privateKeyFile = options.privateKeyFile || null;
            this.publicKeyFile = options.publicKeyFile || null;
    
            const privateKey = fs.readFileSync(this.privateKeyFile);
            const publicKey = fs.readFileSync(this.publicKeyFile, "utf8");
            const pvKey = {
                    key: privateKey,
                    passphrase: "my passphrase"
                };
    
            const ws = fs.createWriteStream(outputPathname);
            const rs = new stream.PassThrough();
            let data = this.inputBuffer.toString('binary');
            rs.end(data);
            __log("rs: ", rs);
    
            rs.on('error', (err) => {__log( "error: ", err);});
    
            rs.on('data', chunk => {
                hasher.update(chunk);
            });
    
            rs.on('end', () => {
    
                const digest = hasher.digest('hex');
                signer.update(digest);
                verifier.update(digest);
    
                const signature = signer.sign(pvKey, 'base64');
    
                const verified = verifier.verify(publicKey, signature, 'base64');
     
                __log("privateKey: ", privateKey);
                __log("publicKey: ", publicKey);
                __log("signature: ", signature);
                __log("digest: ", digest);
                __log("verified: ", verified);
                __log(self.inputBuffer);
                __log(data);
    
    return data;
            });
    });
        }
    
        return SignFile;
    }).call(this);
    
    

    okay I think I have an idea about why it failed: is it because I call the return from inside the callback of the rs.on.('end', callback) ?

    also I really want that module to be working because I saw it was a requested feature even on wkhtmltopdf or phantomjs (and mabe saw it once on jsreport too!)



  • Yes, that is the problem. You need to pass a callback function into your module. Try to play with it. Your general problem is not jsreport but wrong handling of async js calls.



  • Thank you Jan! Will go for it then, still not used of all the callback thing but gonna work it out!!

    By the way, when I'll manage to make that module work proprely, would you be interested to implement it into jsreport? I'm pretty sure that could help others trying to figure it out! =)



  • yes for sure, I looks very promising.

    We have such extension already in place, but it is too heavy. It would be much better if you accomplish it with native js.
    https://github.com/jsreport/jsreport-pdf-password



  • Then it would be my pleasure to share it!

    Just to be clear though, the first step is to achieve this:
    0_1496070784189_certifiedPdf.png

    then after I'll will add the password and extra option such as defined in your extension!



  • Good news.
    There is now a custom jsreport extension which can sign pdf outputs.
    https://jsreport.net/learn/pdf-sign


Log in to reply
 

Looks like your connection to jsreport forum was lost, please wait while we try to reconnect.