diff --git a/CHANGELOG.md b/CHANGELOG.md index 7617c8e0..eea88e5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.13.0 - nodejs: rename in wopi - nodejs: using faviconUrl from WOPI discovery +- nodejs: wopi proof key verification - golang: new integration example - golang: upload files to the server - golang: create blank files and files with sample content diff --git a/web/documentserver-example/nodejs/helpers/wopi/request.js b/web/documentserver-example/nodejs/helpers/wopi/request.js index 880b5efc..a3fdb540 100644 --- a/web/documentserver-example/nodejs/helpers/wopi/request.js +++ b/web/documentserver-example/nodejs/helpers/wopi/request.js @@ -57,6 +57,10 @@ const requestHeaders = Object.freeze({ LockFailureReason: 'X-WOPI-LockFailureReason', LockedByOtherInterface: 'X-WOPI-LockedByOtherInterface', + Proof: 'X-WOPI-Proof', + ProofOld: 'X-WOPI-ProofOld', + Timestamp: 'X-WOPI-Timestamp', + FileConversion: 'X-WOPI-FileConversion', FileName: 'X-WOPI-RequestedName', diff --git a/web/documentserver-example/nodejs/helpers/wopi/tokenValidator.js b/web/documentserver-example/nodejs/helpers/wopi/tokenValidator.js index a4c2242c..61542859 100644 --- a/web/documentserver-example/nodejs/helpers/wopi/tokenValidator.js +++ b/web/documentserver-example/nodejs/helpers/wopi/tokenValidator.js @@ -16,4 +16,45 @@ * */ -exports.isValidToken = (req, res, next) => next(); +const wopiValidator = require('@mercadoeletronico/wopi-proof-validator'); +const DocManager = require('../docManager'); +const reqConsts = require('./request'); +const utils = require('./utils'); + +exports.isValidToken = async (req, res, next) => { + try { + req.DocManager = new DocManager(req, res); + const proofKey = await utils.getProofKey(req.DocManager); + if (!proofKey) { + next(); + return; + } + + const isValid = wopiValidator.check( + { + url: `${req.protocol}://${req.get('host')}${req.originalUrl || req.url}`, + accessToken: req.query.access_token, + timestamp: req.headers[reqConsts.requestHeaders.Timestamp.toLowerCase()], + }, + { + proof: req.headers[reqConsts.requestHeaders.Proof.toLowerCase()], + proofold: req.headers[reqConsts.requestHeaders.ProofOld.toLowerCase()], + }, + { + modulus: proofKey.modulus, + exponent: proofKey.exponent, + oldmodulus: proofKey.oldmodulus, + oldexponent: proofKey.oldexponent, + }, + ); + if (isValid) { + next(); + } else { + console.warn('Proof key verification failed'); + res.status(500).send('Not verified'); + } + } catch (error) { + console.error(error); + res.status(500).send(`Verification error: ${error.message}`); + } +}; diff --git a/web/documentserver-example/nodejs/helpers/wopi/utils.js b/web/documentserver-example/nodejs/helpers/wopi/utils.js index e8abed9b..3a70cc01 100644 --- a/web/documentserver-example/nodejs/helpers/wopi/utils.js +++ b/web/documentserver-example/nodejs/helpers/wopi/utils.js @@ -37,6 +37,7 @@ const requestDiscovery = async function requestDiscovery(DocManager) { return new Promise((resolve, reject) => { const uri = absSiteUrl + configServer.get('wopi.discovery'); const actions = []; + let proofKey = null; // parse url to allow request by relative url after // https://github.com/node-modules/urllib/pull/321/commits/514de1924bf17a38a6c2db2a22a6bc3494c0a959 @@ -77,33 +78,47 @@ const requestDiscovery = async function requestDiscovery(DocManager) { }); }); }); + proofKey = discovery['wopi-discovery']['proof-key']; } } - resolve(actions); + resolve({ actions, proofKey }); }, ); }); }; -// get the wopi discovery information -const getDiscoveryInfo = async function getDiscoveryInfo(DocManager) { - let actions = []; +const getDiscovery = async function getDiscovery(DocManager) { + let discovery = {}; if (cache) return cache; try { - actions = await requestDiscovery(DocManager); + discovery = await requestDiscovery(DocManager); } catch (e) { - return actions; + return discovery; } - cache = actions; + cache = discovery; setTimeout(() => { cache = null; return cache; }, 1000 * 60 * 60); // 1 hour - return actions; + return discovery; +}; + +// get the wopi discovery actions information +const getDiscoveryInfo = async function getDiscoveryInfo(DocManager) { + const discovery = await getDiscovery(DocManager); + + return discovery.actions; +}; + +// get the wopi discovery proof key +const getProofKey = async function getProofKey(DocManager) { + const discovery = await getDiscovery(DocManager); + + return discovery.proofKey; }; // get actions of the specified extension @@ -176,5 +191,6 @@ exports.getEditNewText = getEditNewText; exports.getDiscoveryInfo = getDiscoveryInfo; exports.getAction = getAction; exports.getActions = getActions; +exports.getProofKey = getProofKey; exports.getActionUrl = getActionUrl; exports.getDefaultAction = getDefaultAction; diff --git a/web/documentserver-example/nodejs/helpers/wopi/wopiRouting.js b/web/documentserver-example/nodejs/helpers/wopi/wopiRouting.js index e7edcb42..58d54242 100755 --- a/web/documentserver-example/nodejs/helpers/wopi/wopiRouting.js +++ b/web/documentserver-example/nodejs/helpers/wopi/wopiRouting.js @@ -32,7 +32,7 @@ const getCustomWopiParams = function getCustomWopiParams(query) { let actionParams = ''; const { userid } = query; // user id - tokenParams += (userid ? `&userid=${userid}` : ''); + tokenParams += (userid ? `-userid=${userid}` : ''); const { lang } = query; // language actionParams += (lang ? `&ui=${lang}` : ''); diff --git a/web/documentserver-example/nodejs/package-lock.json b/web/documentserver-example/nodejs/package-lock.json index e2d4313f..40b7db91 100644 --- a/web/documentserver-example/nodejs/package-lock.json +++ b/web/documentserver-example/nodejs/package-lock.json @@ -9,6 +9,7 @@ "version": "1.6.0", "license": "Apache", "dependencies": { + "@mercadoeletronico/wopi-proof-validator": "^1.0.2", "body-parser": "^1.20.3", "config": "^3.3.12", "debug": "^4.3.4", @@ -132,6 +133,18 @@ "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, + "node_modules/@mercadoeletronico/wopi-proof-validator": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@mercadoeletronico/wopi-proof-validator/-/wopi-proof-validator-1.0.2.tgz", + "integrity": "sha512-ywaC2b/wBWhVSC1E8E2E5KgFg+irsr/WtgkgeiopxNbV56OZdLupaVd7aT/lOb1vppJ55TG+w4kaPFVh1tLn9A==", + "license": "Apache-2.0", + "dependencies": { + "cross-env": "^6.0.3", + "debug": "^4.1.1", + "int64-buffer": "^0.99.1007", + "rsa-pem-from-mod-exp": "^0.8.4" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -619,11 +632,26 @@ "resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz", "integrity": "sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==" }, + "node_modules/cross-env": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-6.0.3.tgz", + "integrity": "sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.0" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -1860,6 +1888,15 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/int64-buffer": { + "version": "0.99.1007", + "resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-0.99.1007.tgz", + "integrity": "sha512-XDBEu44oSTqlvCSiOZ/0FoUkpWu/vwjJLGSKDabNISPQNZ5wub1FodGHBljRsrR0IXRPq7SslshZYMuA55CgTQ==", + "license": "MIT", + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -2124,8 +2161,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/jake": { "version": "10.8.7", @@ -2706,7 +2742,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -2906,6 +2941,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rsa-pem-from-mod-exp": { + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/rsa-pem-from-mod-exp/-/rsa-pem-from-mod-exp-0.8.6.tgz", + "integrity": "sha512-c5ouQkOvGHF1qomUUDJGFcXsomeSO2gbEs6hVhMAtlkE1CuaZase/WzoaKFG/EZQuNmq6pw/EMCeEnDvOgCJYQ==", + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3123,7 +3164,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -3135,7 +3175,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -3585,7 +3624,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -3733,6 +3771,17 @@ "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, + "@mercadoeletronico/wopi-proof-validator": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@mercadoeletronico/wopi-proof-validator/-/wopi-proof-validator-1.0.2.tgz", + "integrity": "sha512-ywaC2b/wBWhVSC1E8E2E5KgFg+irsr/WtgkgeiopxNbV56OZdLupaVd7aT/lOb1vppJ55TG+w4kaPFVh1tLn9A==", + "requires": { + "cross-env": "^6.0.3", + "debug": "^4.1.1", + "int64-buffer": "^0.99.1007", + "rsa-pem-from-mod-exp": "^0.8.4" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4091,11 +4140,18 @@ "resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz", "integrity": "sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==" }, + "cross-env": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-6.0.3.tgz", + "integrity": "sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==", + "requires": { + "cross-spawn": "^7.0.0" + } + }, "cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -5033,6 +5089,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "int64-buffer": { + "version": "0.99.1007", + "resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-0.99.1007.tgz", + "integrity": "sha512-XDBEu44oSTqlvCSiOZ/0FoUkpWu/vwjJLGSKDabNISPQNZ5wub1FodGHBljRsrR0IXRPq7SslshZYMuA55CgTQ==" + }, "internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -5207,8 +5268,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "jake": { "version": "10.8.7", @@ -5646,8 +5706,7 @@ "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "path-parse": { "version": "1.0.7", @@ -5780,6 +5839,11 @@ "glob": "^7.1.3" } }, + "rsa-pem-from-mod-exp": { + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/rsa-pem-from-mod-exp/-/rsa-pem-from-mod-exp-0.8.6.tgz", + "integrity": "sha512-c5ouQkOvGHF1qomUUDJGFcXsomeSO2gbEs6hVhMAtlkE1CuaZase/WzoaKFG/EZQuNmq6pw/EMCeEnDvOgCJYQ==" + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5947,7 +6011,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -5955,8 +6018,7 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "side-channel": { "version": "1.0.4", @@ -6284,7 +6346,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "requires": { "isexe": "^2.0.0" } diff --git a/web/documentserver-example/nodejs/package.json b/web/documentserver-example/nodejs/package.json index bfae6a0b..71c03944 100644 --- a/web/documentserver-example/nodejs/package.json +++ b/web/documentserver-example/nodejs/package.json @@ -14,6 +14,7 @@ "url": "https://github.com/ONLYOFFICE/document-server-integration/issues" }, "dependencies": { + "@mercadoeletronico/wopi-proof-validator": "^1.0.2", "body-parser": "^1.20.3", "config": "^3.3.12", "debug": "^4.3.4",