From 347d56bb247ee4fb943b09ba527424699da59498 Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Mon, 1 Jul 2024 13:40:47 +0200 Subject: [PATCH 01/30] chore: don't use django to serve files --- server.js | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/server.js b/server.js index 9753f0b6e..fd128ac16 100644 --- a/server.js +++ b/server.js @@ -8,14 +8,11 @@ const helmet = require("helmet"); const Sentry = require("@sentry/node"); const { Elm } = require("./server-app"); const lib = require("./lib"); -const memoize = require("fast-memoize"); const app = express(); // web app const api = express(); // api app const host = "0.0.0.0"; const express_port = 8001; -const django_port = 8002; -const max_memoize_age = 1000 * 60 * 24; // 24 hours memoization // Env vars const { SENTRY_DSN, MATOMO_HOST, MATOMO_SITE_ID, MATOMO_TOKEN } = process.env; @@ -111,24 +108,33 @@ const cleanRedirect = (url) => (url.startsWith("/") ? url : ""); api.get(/^\/simulator(.*)$/, ({ url }, res) => res.redirect(`/api/textile${cleanRedirect(url)}`)); const getProcesses = async (token) => { - let headers = {}; + let textileFile; + let foodFile; + + // For now, consider that if we prodive a token, we are authenticated + // Next step : use this token to decrypt the files if (token) { - headers["token"] = token; + textileFile = "public/data/textile/processes_impacts.json"; + foodFile = "public/data/textile/processes_impacts.json"; + } else { + textileFile = "public/data/textile/processes.json"; + foodFile = "public/data/textile/processes.json"; } - const processesUrl = `http://127.0.0.1:${django_port}/processes/processes.json`; - const processesRes = await fetch(processesUrl, { headers: headers }); - const processes = await processesRes.json(); - return { processes: processes, status: processesRes.status }; -}; + var textile = JSON.parse(fs.readFileSync(textileFile, "utf8")); + var food = JSON.parse(fs.readFileSync(foodFile, "utf8")); -const memoizedGetProcesses = memoize(getProcesses, { maxAge: max_memoize_age }); + return { + processes: { foodProcesses: food, textileProcesses: textile }, + status: 200, + }; +}; // Note: Text/JSON request body parser (JSON is decoded in Elm) api.all(/(.*)/, bodyParser.json(), async (req, res) => { let result; try { - result = await memoizedGetProcesses(req.headers.token); + result = await getProcesses(req.headers.token); if (result.status != 200) { return res.status(result.status).send(result.processes); } From cb037746989cfe5402f18602b602b1daae6ecd39 Mon Sep 17 00:00:00 2001 From: Nicolas Perriault Date: Mon, 6 May 2024 12:09:20 +0200 Subject: [PATCH 02/30] Add tooling to encrypt and decrypt files. --- .env.sample | 1 + bin/decrypt | 14 +++++++++ bin/encrypt | 14 +++++++++ lib/crypto.js | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 ++ 5 files changed, 114 insertions(+) create mode 100755 bin/decrypt create mode 100755 bin/encrypt create mode 100644 lib/crypto.js diff --git a/.env.sample b/.env.sample index c85322af2..77b120231 100644 --- a/.env.sample +++ b/.env.sample @@ -4,6 +4,7 @@ DEFAULT_FROM_EMAIL=bar@foo.baz DJANGO_DEBUG=True DJANGO_SECRET_KEY=please-change-this DJANGO_BYPASS_AUTH=False +ENCRYPTION_KEY=please-change-this EMAIL_HOST_PASSWORD=please-change-this EMAIL_HOST_USER=please-change-this ENABLE_FOOD_SECTION=True diff --git a/bin/decrypt b/bin/decrypt new file mode 100755 index 000000000..ef1dbc0e4 --- /dev/null +++ b/bin/decrypt @@ -0,0 +1,14 @@ +#!/usr/bin/env node + +const { decryptFile } = require("../lib/crypto"); + +const path = `${__dirname}/../${process.argv[2]}`; +console.log(`Decrypting ${path}…`); + +try { + decryptFile(path); + console.log("Done."); +} catch (err) { + console.error(`Couldn't decrypt ${path}:\n> ${err}\nWas it encoded?`); + process.exit(1); +} diff --git a/bin/encrypt b/bin/encrypt new file mode 100755 index 000000000..35dd4c432 --- /dev/null +++ b/bin/encrypt @@ -0,0 +1,14 @@ +#!/usr/bin/env node + +const { encryptFile } = require("../lib/crypto"); + +const path = `${__dirname}/../${process.argv[2]}`; +console.log(`Encrypting ${path}…`); + +try { + encryptFile(path); + console.log("Done."); +} catch (err) { + console.error(`Couldn't encrypt ${path}:\n> ${err}`); + process.exit(1); +} diff --git a/lib/crypto.js b/lib/crypto.js new file mode 100644 index 000000000..f75c0ac29 --- /dev/null +++ b/lib/crypto.js @@ -0,0 +1,83 @@ +require("dotenv").config(); +const fs = require("fs"); +const crypto = require("crypto"); + +// Secret key for encrypting and decrypting things +// If you need to generate a first secret key: crypto.createHash("sha512").digest("hex").substring(0, 32) +// Then store the result in an ENCRYPTION_KEY env var and never expose it publicly! +const { ENCRYPTION_KEY } = process.env; + +function encrypt(text, secret_key = ENCRYPTION_KEY) { + validateSecretKey(secret_key); + validateNotAlreadyEncrypted(text); + const iv = crypto.randomBytes(16); + const cipher = crypto.createCipheriv("aes-256-cbc", Buffer.from(secret_key), iv); + return { + iv: iv.toString("hex"), + encrypted: Buffer.concat([cipher.update(text), cipher.final()]).toString("hex"), + }; +} + +function decrypt({ encrypted, iv }, secret_key = ENCRYPTION_KEY) { + validateSecretKey(secret_key); + const decipher = crypto.createDecipheriv( + "aes-256-cbc", + Buffer.from(secret_key), + Buffer.from(iv, "hex"), + ); + return Buffer.concat([ + decipher.update(Buffer.from(encrypted, "hex")), + decipher.final(), + ]).toString(); +} + +function encryptFile(path, secret_key = ENCRYPTION_KEY) { + const contents = fs.readFileSync(path).toString("utf-8"); + const encryptedContents = encrypt(contents, secret_key); + fs.writeFileSync(path, JSON.stringify(encryptedContents)); +} + +function decryptFile(path, secret_key = ENCRYPTION_KEY) { + const encryptedContents = fs.readFileSync(path).toString("utf-8"); + fs.writeFileSync(path, decrypt(JSON.parse(encryptedContents), secret_key)); +} + +function validateNotAlreadyEncrypted(contents) { + try { + // Attempting at deciphering provided text should raise an expected Error + decrypt(JSON.parse(contents)); + } catch (err) { + return; + } + // If no error has been raised, we're likely to face already encypted text + throw new Error("Contents are already encrypted."); +} + +function validateSecretKey(key) { + if (String(key).length !== 32) { + throw new Error("Invalid secret key provided, which must be 32c length."); + } +} + +function demo() { + const textToEncrypt = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; + console.log("Texte à chiffrer:", textToEncrypt); + const encrypted = encrypt(textToEncrypt); + console.log("Données chiffrées:", encrypted); + console.log("Données déchiffrées:", decrypt(encrypted)); +} + +function demoFile() { + const path = `${__dirname}/../public/data/textile/processes_impacts.json`; + encryptFile(path); + decryptFile(path); +} + +module.exports = { + decrypt, + decryptFile, + demo, + demoFile, + encrypt, + encryptFile, +}; diff --git a/package.json b/package.json index f3c43aab8..e8e2aef40 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "build": "npm run server:build && rimraf dist && npm run build:init && parcel build index.html --public-url ./", "build:init": "./bin/update-version.sh && mkdir -p dist && cp -r public/* dist/ && npm run db:build", "processes:build": "elm make src/ComputeAggregated.elm --output=compute-aggregated-app.js && node compute-aggregated.js", + "processes:decrypt": "./bin/decrypt public/data/textile/processes_impacts.json", + "processes:encrypt": "./bin/encrypt public/data/textile/processes_impacts.json", "db:build": "./bin/build-db", "lint:prettier": "prettier --config .prettierrc --check", "lint:prettier:all": "npm run lint:prettier -- .", From 43820ee6430a34ea0dd869df712bb7e6fff18d00 Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Mon, 1 Jul 2024 15:05:01 +0200 Subject: [PATCH 03/30] feat: encrypt files server side --- .proxyrc.json | 2 +- bin/generate-encrypt-key | 5 +++++ server.js | 46 +++++++++++++++++++++++----------------- 3 files changed, 32 insertions(+), 21 deletions(-) create mode 100755 bin/generate-encrypt-key diff --git a/.proxyrc.json b/.proxyrc.json index 8fd60ac88..96ac4e24d 100644 --- a/.proxyrc.json +++ b/.proxyrc.json @@ -6,7 +6,7 @@ "target": "http://127.0.0.1:8002/" }, "/processes/": { - "target": "http://127.0.0.1:8002/" + "target": "http://127.0.0.1:8001/" }, "/admin/": { "target": "http://127.0.0.1:8002/" diff --git a/bin/generate-encrypt-key b/bin/generate-encrypt-key new file mode 100755 index 000000000..5c6121ba4 --- /dev/null +++ b/bin/generate-encrypt-key @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +const crypto = require("crypto"); +const key = crypto.createHash("sha512").digest("hex").substring(0, 32); +process.stdout.write(key + "\n"); diff --git a/server.js b/server.js index fd128ac16..4b4cf50e9 100644 --- a/server.js +++ b/server.js @@ -8,6 +8,7 @@ const helmet = require("helmet"); const Sentry = require("@sentry/node"); const { Elm } = require("./server-app"); const lib = require("./lib"); +const { encrypt } = require("./lib/crypto"); const app = express(); // web app const api = express(); // api app @@ -82,6 +83,16 @@ app.get("/accessibilite", (_, res) => res.redirect("/#/pages/accessibilité")); app.get("/mentions-legales", (_, res) => res.redirect("/#/pages/mentions-légales")); app.get("/stats", (_, res) => res.redirect("/#/stats")); +app.get("/processes/processes.json", async (_, res) => { + try { + result = await getProcesses(); + return res.status(result.status).send(result.processes); + } catch (err) { + console.error(err.message); + return res.status(500).send("Error while retrieving the processes"); + } +}); + // API const openApiContents = yaml.load(fs.readFileSync("openapi.yaml")); @@ -107,25 +118,23 @@ api.get(/^\/products$/, (_, res) => res.redirect("textile/products")); const cleanRedirect = (url) => (url.startsWith("/") ? url : ""); api.get(/^\/simulator(.*)$/, ({ url }, res) => res.redirect(`/api/textile${cleanRedirect(url)}`)); -const getProcesses = async (token) => { - let textileFile; - let foodFile; - - // For now, consider that if we prodive a token, we are authenticated - // Next step : use this token to decrypt the files - if (token) { - textileFile = "public/data/textile/processes_impacts.json"; - foodFile = "public/data/textile/processes_impacts.json"; - } else { - textileFile = "public/data/textile/processes.json"; - foodFile = "public/data/textile/processes.json"; - } +const getProcesses = async (encrypted = true) => { + const textileFile = "public/data/textile/processes_impacts.json"; + const foodFile = "public/data/textile/processes_impacts.json"; + + const { ENCRYPTION_KEY } = process.env; - var textile = JSON.parse(fs.readFileSync(textileFile, "utf8")); - var food = JSON.parse(fs.readFileSync(foodFile, "utf8")); + let processes = { + foodProcesses: JSON.parse(fs.readFileSync(foodFile, "utf8")), + textileProcesses: JSON.parse(fs.readFileSync(textileFile, "utf8")), + }; + + if (encrypted) { + processes = encrypt(JSON.stringify(processes), ENCRYPTION_KEY); + } return { - processes: { foodProcesses: food, textileProcesses: textile }, + processes: processes, status: 200, }; }; @@ -134,10 +143,7 @@ const getProcesses = async (token) => { api.all(/(.*)/, bodyParser.json(), async (req, res) => { let result; try { - result = await getProcesses(req.headers.token); - if (result.status != 200) { - return res.status(result.status).send(result.processes); - } + result = await getProcesses(false); } catch (err) { console.error(err.message); return res.status(500).send("Error while retrieving the processes"); From b95cc9bdb3a07684a9926f5058a744ff7ed0d67f Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Mon, 1 Jul 2024 15:31:28 +0200 Subject: [PATCH 04/30] fix: modify nginx conf --- servers.conf.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servers.conf.erb b/servers.conf.erb index 3e959e54f..29b1c1100 100644 --- a/servers.conf.erb +++ b/servers.conf.erb @@ -31,7 +31,7 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ; } location /processes { - proxy_pass http://localhost:8002; + proxy_pass http://localhost:8001; proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ; From acf8dbcc0422883ceae73e5f47c7fb8cb1a5e823 Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Mon, 1 Jul 2024 15:32:24 +0200 Subject: [PATCH 05/30] fix: we don't need to encrypt files directly --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index e8e2aef40..f3c43aab8 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,6 @@ "build": "npm run server:build && rimraf dist && npm run build:init && parcel build index.html --public-url ./", "build:init": "./bin/update-version.sh && mkdir -p dist && cp -r public/* dist/ && npm run db:build", "processes:build": "elm make src/ComputeAggregated.elm --output=compute-aggregated-app.js && node compute-aggregated.js", - "processes:decrypt": "./bin/decrypt public/data/textile/processes_impacts.json", - "processes:encrypt": "./bin/encrypt public/data/textile/processes_impacts.json", "db:build": "./bin/build-db", "lint:prettier": "prettier --config .prettierrc --check", "lint:prettier:all": "npm run lint:prettier -- .", From 162890f7c7f4f57f35bd4fe472fae33cfb6ebb87 Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Mon, 1 Jul 2024 15:49:01 +0200 Subject: [PATCH 06/30] fix: we don't need status --- server.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/server.js b/server.js index 4b4cf50e9..62b23c66b 100644 --- a/server.js +++ b/server.js @@ -85,8 +85,8 @@ app.get("/stats", (_, res) => res.redirect("/#/stats")); app.get("/processes/processes.json", async (_, res) => { try { - result = await getProcesses(); - return res.status(result.status).send(result.processes); + const processes = await getProcesses(); + return res.status(200).send(processes); } catch (err) { console.error(err.message); return res.status(500).send("Error while retrieving the processes"); @@ -133,17 +133,14 @@ const getProcesses = async (encrypted = true) => { processes = encrypt(JSON.stringify(processes), ENCRYPTION_KEY); } - return { - processes: processes, - status: 200, - }; + return processes; }; // Note: Text/JSON request body parser (JSON is decoded in Elm) api.all(/(.*)/, bodyParser.json(), async (req, res) => { - let result; + let processes; try { - result = await getProcesses(false); + processes = await getProcesses(false); } catch (err) { console.error(err.message); return res.status(500).send("Error while retrieving the processes"); @@ -153,7 +150,7 @@ api.all(/(.*)/, bodyParser.json(), async (req, res) => { method: req.method, url: req.url, body: req.body, - processes: result.processes, + processes: processes, jsResponseHandler: ({ status, body }) => { apiTracker.track(status, req); res.status(status).send(body); From affaab4042d65635ad82fb55d1d0ed52caac971f Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Mon, 1 Jul 2024 16:13:56 +0200 Subject: [PATCH 07/30] chore: remove fast-memoize --- package-lock.json | 4 ++-- package.json | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cc74ede05..fc4948d08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,6 @@ "dotenv": "^16.4.5", "elm": "^0.19.1-6", "express": "^4.19.2", - "fast-memoize": "^2.5.2", "helmet": "^7.1.0", "highcharts": "^11.4.0", "js-yaml": "^4.1.0", @@ -6368,7 +6367,8 @@ "node_modules/fast-memoize": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", - "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==" + "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", + "dev": true }, "node_modules/fast-safe-stringify": { "version": "2.1.1", diff --git a/package.json b/package.json index f3c43aab8..7791fdf6c 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "dotenv": "^16.4.5", "elm": "^0.19.1-6", "express": "^4.19.2", - "fast-memoize": "^2.5.2", "helmet": "^7.1.0", "highcharts": "^11.4.0", "js-yaml": "^4.1.0", From 558dc88fdad2561a5e124e708b52c8125abf33df Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Mon, 1 Jul 2024 16:16:18 +0200 Subject: [PATCH 08/30] fix: read processes only once --- server.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/server.js b/server.js index 62b23c66b..78ea6dd6e 100644 --- a/server.js +++ b/server.js @@ -100,6 +100,16 @@ const openApiContents = yaml.load(fs.readFileSync("openapi.yaml")); // Matomo const apiTracker = lib.setupTracker(openApiContents); +// Detailed processes files + +const textileFile = "public/data/textile/processes_impacts.json"; +const foodFile = "public/data/textile/processes_impacts.json"; + +const processes = { + foodProcesses: JSON.parse(fs.readFileSync(foodFile, "utf8")), + textileProcesses: JSON.parse(fs.readFileSync(textileFile, "utf8")), +}; + const elmApp = Elm.Server.init(); elmApp.ports.output.subscribe(({ status, body, jsResponseHandler }) => { @@ -119,21 +129,13 @@ const cleanRedirect = (url) => (url.startsWith("/") ? url : ""); api.get(/^\/simulator(.*)$/, ({ url }, res) => res.redirect(`/api/textile${cleanRedirect(url)}`)); const getProcesses = async (encrypted = true) => { - const textileFile = "public/data/textile/processes_impacts.json"; - const foodFile = "public/data/textile/processes_impacts.json"; - const { ENCRYPTION_KEY } = process.env; - let processes = { - foodProcesses: JSON.parse(fs.readFileSync(foodFile, "utf8")), - textileProcesses: JSON.parse(fs.readFileSync(textileFile, "utf8")), - }; - if (encrypted) { - processes = encrypt(JSON.stringify(processes), ENCRYPTION_KEY); + return encrypt(JSON.stringify(processes), ENCRYPTION_KEY); + } else { + return processes; } - - return processes; }; // Note: Text/JSON request body parser (JSON is decoded in Elm) From 0f4278adc5c0fa719569631df9c4f6436e7dc370 Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Mon, 1 Jul 2024 16:29:40 +0200 Subject: [PATCH 09/30] fix: compute everything at server start --- server.js | 37 ++++++++----------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/server.js b/server.js index 78ea6dd6e..1ec002245 100644 --- a/server.js +++ b/server.js @@ -83,16 +83,6 @@ app.get("/accessibilite", (_, res) => res.redirect("/#/pages/accessibilité")); app.get("/mentions-legales", (_, res) => res.redirect("/#/pages/mentions-légales")); app.get("/stats", (_, res) => res.redirect("/#/stats")); -app.get("/processes/processes.json", async (_, res) => { - try { - const processes = await getProcesses(); - return res.status(200).send(processes); - } catch (err) { - console.error(err.message); - return res.status(500).send("Error while retrieving the processes"); - } -}); - // API const openApiContents = yaml.load(fs.readFileSync("openapi.yaml")); @@ -110,6 +100,13 @@ const processes = { textileProcesses: JSON.parse(fs.readFileSync(textileFile, "utf8")), }; +const { ENCRYPTION_KEY } = process.env; +const encryptedProcesses = encrypt(JSON.stringify(processes), ENCRYPTION_KEY); + +app.get("/processes/processes.json", async (_, res) => { + return res.status(200).send(encryptedProcesses); +}); + const elmApp = Elm.Server.init(); elmApp.ports.output.subscribe(({ status, body, jsResponseHandler }) => { @@ -128,31 +125,13 @@ api.get(/^\/products$/, (_, res) => res.redirect("textile/products")); const cleanRedirect = (url) => (url.startsWith("/") ? url : ""); api.get(/^\/simulator(.*)$/, ({ url }, res) => res.redirect(`/api/textile${cleanRedirect(url)}`)); -const getProcesses = async (encrypted = true) => { - const { ENCRYPTION_KEY } = process.env; - - if (encrypted) { - return encrypt(JSON.stringify(processes), ENCRYPTION_KEY); - } else { - return processes; - } -}; - // Note: Text/JSON request body parser (JSON is decoded in Elm) api.all(/(.*)/, bodyParser.json(), async (req, res) => { - let processes; - try { - processes = await getProcesses(false); - } catch (err) { - console.error(err.message); - return res.status(500).send("Error while retrieving the processes"); - } - elmApp.ports.input.send({ method: req.method, url: req.url, body: req.body, - processes: processes, + processes, jsResponseHandler: ({ status, body }) => { apiTracker.track(status, req); res.status(status).send(body); From b43855d5d7aaafb06e102831d8bacc62423def3b Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Mon, 1 Jul 2024 16:43:12 +0200 Subject: [PATCH 10/30] =?UTF-8?q?fix:=20add=2032=C2=A0chars=20encryption?= =?UTF-8?q?=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.sample | 2 +- .github/workflows/node.js.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.env.sample b/.env.sample index 77b120231..a4c943173 100644 --- a/.env.sample +++ b/.env.sample @@ -4,7 +4,7 @@ DEFAULT_FROM_EMAIL=bar@foo.baz DJANGO_DEBUG=True DJANGO_SECRET_KEY=please-change-this DJANGO_BYPASS_AUTH=False -ENCRYPTION_KEY=please-change-this +ENCRYPTION_KEY=my_secret_encryptionkey_changeit EMAIL_HOST_PASSWORD=please-change-this EMAIL_HOST_USER=please-change-this ENABLE_FOOD_SECTION=True diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 9cf135952..e137c6db1 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -83,4 +83,5 @@ jobs: - name: Run server tests env: DJANGO_BYPASS_AUTH: True + ENCRYPTION_KEY: my_secret_encryptionkey_changeit run: pipenv run backend/update.sh && npm run test:server-ci && npm run test:backend From aa1515a08cce9efab7afae8ec5ac75863fe68fbd Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Tue, 2 Jul 2024 11:36:51 +0200 Subject: [PATCH 11/30] fix: processes values should be strings --- server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index 1ec002245..7d8628eaa 100644 --- a/server.js +++ b/server.js @@ -96,8 +96,8 @@ const textileFile = "public/data/textile/processes_impacts.json"; const foodFile = "public/data/textile/processes_impacts.json"; const processes = { - foodProcesses: JSON.parse(fs.readFileSync(foodFile, "utf8")), - textileProcesses: JSON.parse(fs.readFileSync(textileFile, "utf8")), + foodProcesses: fs.readFileSync(foodFile, "utf8"), + textileProcesses: fs.readFileSync(textileFile, "utf8"), }; const { ENCRYPTION_KEY } = process.env; From ac4733f065030e67ea0687fff3c0f7dc4acbca20 Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Tue, 2 Jul 2024 11:50:12 +0200 Subject: [PATCH 12/30] fix: food impacts path --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 7d8628eaa..96291ea87 100644 --- a/server.js +++ b/server.js @@ -93,7 +93,7 @@ const apiTracker = lib.setupTracker(openApiContents); // Detailed processes files const textileFile = "public/data/textile/processes_impacts.json"; -const foodFile = "public/data/textile/processes_impacts.json"; +const foodFile = "public/data/food/processes_impacts.json"; const processes = { foodProcesses: fs.readFileSync(foodFile, "utf8"), From 7ab7e6145a1a536e0484c46ec361152cfa107d3a Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Tue, 2 Jul 2024 11:55:41 +0200 Subject: [PATCH 13/30] chore: Django app `processes` is not needed anymore --- backend/backend/urls.py | 1 - backend/processes/urls.py | 7 ----- backend/processes/views.py | 58 -------------------------------------- 3 files changed, 66 deletions(-) delete mode 100644 backend/processes/urls.py delete mode 100644 backend/processes/views.py diff --git a/backend/backend/urls.py b/backend/backend/urls.py index 132772417..140ed8fa9 100644 --- a/backend/backend/urls.py +++ b/backend/backend/urls.py @@ -25,7 +25,6 @@ urlpatterns = [ path("admin/", admin_site.urls), path("accounts/", include("authentication.urls")), - path("processes/", include("processes.urls")), ] if settings.DEBUG: diff --git a/backend/processes/urls.py b/backend/processes/urls.py deleted file mode 100644 index f54eefe03..000000000 --- a/backend/processes/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.urls import path - -from .views import processes - -urlpatterns = [ - path("processes.json", processes), -] diff --git a/backend/processes/views.py b/backend/processes/views.py deleted file mode 100644 index 1b38278a8..000000000 --- a/backend/processes/views.py +++ /dev/null @@ -1,58 +0,0 @@ -from os.path import join - -from authentication.views import is_token_valid -from django.conf import settings -from django.http import JsonResponse -from django.utils.translation import gettext_lazy as _ - -PUBLIC_FOLDER = join(settings.GITROOT, "public", "data") - - -# Pre-load processes files. -with open(join(PUBLIC_FOLDER, "food", "processes.json"), "r") as f: - food_processes = f.read() - -with open(join(PUBLIC_FOLDER, "textile", "processes.json"), "r") as f: - textile_processes = f.read() - -with open(join(PUBLIC_FOLDER, "food", "processes_impacts.json"), "r") as f: - food_processes_detailed = f.read() - -with open(join(PUBLIC_FOLDER, "textile", "processes_impacts.json"), "r") as f: - textile_processes_detailed = f.read() - - -processes_not_detailed = { - "foodProcesses": food_processes, - "textileProcesses": textile_processes, -} - -processes_detailed = { - "foodProcesses": food_processes_detailed, - "textileProcesses": textile_processes_detailed, -} - - -def processes(request): - token = request.headers.get("token") - - if settings.BYPASS_AUTH: - return JsonResponse(processes_detailed) - else: - if token: - # Token auth - if is_token_valid(token): - return JsonResponse(processes_detailed) - else: - return JsonResponse( - {"error": _("This token isn't valid")}, - status=401, - ) - else: - u = request.user - if u.is_authenticated: - # Cookie auth - return JsonResponse(processes_detailed) - else: - # No auth - return JsonResponse(processes_not_detailed) From cac6ae07f160994a098b8e7d4760fc8e04be8613 Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Wed, 3 Jul 2024 11:52:32 +0200 Subject: [PATCH 14/30] feat: add RSA crypt/encrypt POC --- Pipfile | 2 + Pipfile.lock | 153 +++++++++++++++++++++++++++++++++++++++------ bin/encrypt-rsa.py | 83 ++++++++++++++++++++++++ 3 files changed, 219 insertions(+), 19 deletions(-) create mode 100755 bin/encrypt-rsa.py diff --git a/Pipfile b/Pipfile index 5454e07d8..e343bdd72 100644 --- a/Pipfile +++ b/Pipfile @@ -15,5 +15,7 @@ pandas = "1.5.3" GitPython = "3.1.43" pre-commit = "*" numpy = ">=1,<2" +cryptography = "*" +jwcrypto = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 74c0e5ef8..9e90ad139 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "15013b0585bd571dcc2e3e58ac7db175ebdd7adf7159c55d5209b897dd931231" + "sha256": "b28b6f92a95812a821386c5b698252c724d176dc1786d9301e52a554578a2a1c" }, "pipfile-spec": 6, "requires": {}, @@ -22,6 +22,64 @@ "markers": "python_version >= '3.8'", "version": "==3.8.1" }, + "cffi": { + "hashes": [ + "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", + "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", + "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", + "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", + "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", + "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", + "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", + "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", + "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", + "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", + "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", + "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", + "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", + "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", + "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", + "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", + "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", + "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", + "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", + "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", + "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", + "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", + "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", + "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", + "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", + "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", + "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", + "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", + "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", + "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", + "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", + "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", + "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", + "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", + "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", + "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", + "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", + "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", + "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", + "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", + "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", + "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", + "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", + "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", + "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", + "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", + "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", + "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", + "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", + "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", + "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", + "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==1.16.0" + }, "cfgv": { "hashes": [ "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", @@ -30,6 +88,45 @@ "markers": "python_version >= '3.8'", "version": "==3.4.0" }, + "cryptography": { + "hashes": [ + "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad", + "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583", + "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b", + "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c", + "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1", + "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648", + "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949", + "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba", + "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c", + "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9", + "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d", + "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c", + "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e", + "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2", + "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d", + "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7", + "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70", + "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2", + "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7", + "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14", + "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe", + "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e", + "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71", + "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961", + "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7", + "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c", + "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28", + "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842", + "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902", + "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801", + "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a", + "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==42.0.8" + }, "distlib": { "hashes": [ "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784", @@ -161,6 +258,15 @@ "markers": "python_version >= '3.8'", "version": "==2.5.36" }, + "jwcrypto": { + "hashes": [ + "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789", + "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.5.6" + }, "nodeenv": { "hashes": [ "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", @@ -291,6 +397,14 @@ "markers": "python_version >= '3.7'", "version": "==2.9.9" }, + "pycparser": { + "hashes": [ + "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" + ], + "markers": "python_version >= '3.8'", + "version": "==2.22" + }, "python-dateutil": { "hashes": [ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", @@ -373,27 +487,28 @@ }, "ruff": { "hashes": [ - "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6", - "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739", - "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d", - "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695", - "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804", - "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815", - "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac", - "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7", - "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631", - "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e", - "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e", - "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef", - "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6", - "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784", - "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81", - "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0", - "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca" + "sha256:2c4dfcd8d34b143916994b3876b63d53f56724c03f8c1a33a253b7b1e6bf2a7d", + "sha256:38f3b8327b3cb43474559d435f5fa65dacf723351c159ed0dc567f7ab735d1b6", + "sha256:46e193b36f2255729ad34a49c9a997d506e58f08555366b2108783b3064a0e1e", + "sha256:49141d267100f5ceff541b4e06552e98527870eafa1acc9dec9139c9ec5af64c", + "sha256:7594f8df5404a5c5c8f64b8311169879f6cf42142da644c7e0ba3c3f14130370", + "sha256:81e5facfc9f4a674c6a78c64d38becfbd5e4f739c31fcd9ce44c849f1fad9e4c", + "sha256:9dc5cfd3558f14513ed0d5b70ce531e28ea81a8a3b1b07f0f48421a3d9e7d80a", + "sha256:adc7012d6ec85032bc4e9065110df205752d64010bed5f958d25dbee9ce35de3", + "sha256:b1a321c4f68809fddd9b282fab6a8d8db796b270fff44722589a8b946925a2a8", + "sha256:cd096e23c6a4f9c819525a437fa0a99d1c67a1b6bb30948d46f33afbc53596cf", + "sha256:d2ffbc3715a52b037bcb0f6ff524a9367f642cdc5817944f6af5479bbb2eb50e", + "sha256:d505fb93b0fabef974b168d9b27c3960714d2ecda24b6ffa6a87ac432905ea38", + "sha256:db3ca35265de239a1176d56a464b51557fce41095c37d6c406e658cf80bbb362", + "sha256:e589e27971c2a3efff3fadafb16e5aef7ff93250f0134ec4b52052b673cf988d", + "sha256:e9118f60091047444c1b90952736ee7b1792910cab56e9b9a9ac20af94cd0440", + "sha256:eb641b5873492cf9bd45bc9c5ae5320648218e04386a5f0c264ad6ccce8226a1", + "sha256:ed5c4df5c1fb4518abcb57725b576659542bdbe93366f4f329e8f398c4b71178", + "sha256:ee770ea8ab38918f34e7560a597cc0a8c9a193aaa01bfbd879ef43cb06bd9c4c" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==0.4.10" + "version": "==0.5.0" }, "six": { "hashes": [ diff --git a/bin/encrypt-rsa.py b/bin/encrypt-rsa.py new file mode 100755 index 000000000..689578fbf --- /dev/null +++ b/bin/encrypt-rsa.py @@ -0,0 +1,83 @@ +#!/bin/env python +from cryptography.hazmat.primitives.serialization import load_pem_public_key +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding +from jwcrypto import jwk +import json + +# Javascript code to generate RSA keys +# +# let keyPair = await window.crypto.subtle.generateKey( +# { +# name: "RSA-OAEP", +# modulusLength: 4096, +# publicExponent: new Uint8Array([1, 0, 1]), +# hash: "SHA-256", +# }, +# true, +# ["encrypt", "decrypt"], +# ); +# +# window.crypto.subtle.exportKey("jwk", keyPair.publicKey).then( +# function(keydata) { +# publicKeyhold = keydata; +# publicKeyJson = JSON.stringify(publicKeyhold); +# console.log(publicKeyJson); +# } +# ); +# {"alg":"RSA-OAEP-256","e":"AQAB","ext":true,"key_ops":["encrypt"],"kty":"RSA","n":"sMZZaPJGTbQRhJyin5SBCbKa0pFMmfbpXea9ES9tH5m87Ec-uFUe7b62Xy72wwTnl5OVNdWK0Ge2Zh_d8vdryo0o7r58twsoHquKEIKdtpdoAPMdDClvherka4bTXrzbDDwnbOMX1PCPmo7hpViJEHL4rtlm0kwkxOhemAd50NFjDIhCqwQ1Um6zunF4bzuA1htq3MKtxeI7p2TR6CGIq1ztgNHRTh-aPR7ItOT9kKwQY1zlDN6mWFr_XZeVfVdd-Acd9GzijF4_-_TF3T95VYH5hCEHAnI9DG7Smz6xh5rfWq_muRMD8llAxbKdD6v6FhU56uXotU0nF4sIHJkoyNifWVHbKEr6CVMK5F0x4COmq5Q4vVq1QABkhL90tWWXtIoFaxfnJVIDeumMIqREw9j5p4b2F-GJnoVaY1CZQfZm9o1rZETI6bCtndXsyqpCgl3vBFEr5DN4AA7gFrAdcQlCve4l5cVCnZBGEKtKehNaXtcspEjogYq9OhJSHMoiK-5zqMHmx2k9s1eBfCq5_VeiuK3oAo3DknkX66gaGOe5M69n1zyJB32N_pVckyP5z2z23KhAtoLBcXKpkiCuFS9tKzejvOmT5EhugQGzoHyEmGhzYlLyG_jTefRhHmX4NL3L3xdQRcI2hC0rcFxaUbDc4CQUyhNVx9ZlpXgluPU"} +# +# Result of the python encryption +# +# var cipherText = "\xa0w\xd8n\x8dx\xdfsH\xc6\x94z\x04\xe6<\xc0\x17)P\xaaW\xa8\xe6L\x98K\xec>\xb5\x0b\x88\xc6\xd8\xa4~\x8a\xb4\xd6r\x8e\xd1/7\x98\x9aGd\x9b\x93\xccp\x10u\xac\xfaI\xd1 @\xe8\x17\xfcO\xaa\x11\xd6I\xb4\x99\xa3\xc6\xce\x1f\x00\x06c\xadJZ\nM\xaaFj\xac\xba\xb6'\xf06\x16F\xc8D\x05\x16F\x10\x88\xa9m\xbf\xb0BO\x91\xd7\x8e\xad\xb9\x13\x8c3\xccp\x83\xc1\xcd\x05\xab\xb1[\xeb\xd8\x07\x82\xack\xe9\xf9\xf2\x0f\xef\xda\xd6\xb2d\xd5\x0e\xad\xa3\xbc\x9e\x94\x9d\x19\xc4\xf0^\xe7(`n\xcai*\x0fD\x06\xf1\xcePm\x19\xdf\x90\xd9]\xd2\xc479\n\x02\xa4'\x02\x16\xba\xdd\xd0\x1e\xec\x87N\xb2|\xf6o\xbc\x9fu\xa2H\x04\xd6\xd8= Date: Thu, 4 Jul 2024 09:30:05 +0200 Subject: [PATCH 15/30] chore: use decorators to test method verbs --- backend/authentication/views.py | 124 +++++++++++++++++--------------- 1 file changed, 66 insertions(+), 58 deletions(-) diff --git a/backend/authentication/views.py b/backend/authentication/views.py index 9b092d2c4..3e1438e03 100644 --- a/backend/authentication/views.py +++ b/backend/authentication/views.py @@ -3,7 +3,7 @@ from django.conf import settings from django.core.exceptions import PermissionDenied -from django.http import Http404, JsonResponse, response +from django.http import JsonResponse, response from django.utils.translation import gettext_lazy as _ from mailauth.views import ( LoginTokenView as MailauthLoginTokenView, @@ -13,55 +13,68 @@ ) from authentication.models import EcobalyseUser +from django.views.decorators.http import require_http_methods from .forms import EmailLoginForm, RegistrationForm logger = logging.getLogger(__name__) -def register(request): - """render a form to provide an email to register""" - if request.method == "POST": - try: - form = RegistrationForm( - request=request, data=json.loads(request.body.decode("utf-8")) - ) - except json.JSONDecodeError: - return JsonResponse( - {"success": False, "msg": _("Invalid json in the POST request")} - ) - if form.is_valid(): - form.save() - timeout = settings.LOGIN_URL_TIMEOUT - return JsonResponse( - { - "success": True, - "msg": _("The link is valid for %d min") % (timeout / 60) - if timeout is not None - else _("The link does not expire"), - } - ) +def authenticated_user(view_func): + def wrapper_func(request, *args, **kwargs): + if request.user.is_authenticated: + return view_func(request, *args, **kwargs) + else: - errors = { - k: " ".join(v) for k, v in (form.errors.items() if form.errors else []) - } - if ( - errors.get("email") - == "Un objet Utilisateur avec ce champ Adresse électronique existe déjà." - ): - errors["email"] = _( - "You seem already registered. Try using the connection tab." - ) return JsonResponse( - { - "success": False, - "msg": _("Your form has errors: ") - + " ".join([f"{k}: {v}" for k, v in errors.items()]), - "errors": errors, - } + {"error": _("You must be authenticated to access this page")}, + status=401, ) + + return wrapper_func + + +@require_http_methods(["POST"]) +def register(request): + """render a form to provide an email to register""" + try: + form = RegistrationForm( + request=request, data=json.loads(request.body.decode("utf-8")) + ) + except json.JSONDecodeError: + return JsonResponse( + {"success": False, "msg": _("Invalid json in the POST request")} + ) + if form.is_valid(): + form.save() + timeout = settings.LOGIN_URL_TIMEOUT + return JsonResponse( + { + "success": True, + "msg": _("The link is valid for %d min") % (timeout / 60) + if timeout is not None + else _("The link does not expire"), + } + ) else: - raise Http404("Only POST here") + errors = { + k: " ".join(v) for k, v in (form.errors.items() if form.errors else []) + } + if ( + errors.get("email") + == "Un objet Utilisateur avec ce champ Adresse électronique existe déjà." + ): + errors["email"] = _( + "You seem already registered. Try using the connection tab." + ) + return JsonResponse( + { + "success": False, + "msg": _("Your form has errors: ") + + " ".join([f"{k}: {v}" for k, v in errors.items()]), + "errors": errors, + } + ) class LoginView(MailauthLoginView): @@ -90,25 +103,20 @@ def post(self, request, *a, **kw): return JsonResponse({"success": False, "msg": _("Invalid form data")}) +@require_http_methods(["GET"]) +@authenticated_user def profile(request): - if request.method == "GET": - u = request.user - if u.is_authenticated: - return JsonResponse( - { - "email": u.email, - "first_name": u.first_name, - "last_name": u.last_name, - "organization": u.organization, - "terms_of_use": u.terms_of_use, - "token": u.token, - } - ) - else: - return JsonResponse( - {"error": _("You must be authenticated to access this page")}, - status=401, - ) + u = request.user + return JsonResponse( + { + "email": u.email, + "first_name": u.first_name, + "last_name": u.last_name, + "organization": u.organization, + "terms_of_use": u.terms_of_use, + "token": u.token, + } + ) def is_token_valid(token): From a269bf6b097748e11ae49b67776d8317760abc80 Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Thu, 4 Jul 2024 09:32:25 +0200 Subject: [PATCH 16/30] test: simplify test output --- Pipfile | 2 - Pipfile.lock | 116 +---------------------------- backend/authentication/tests.py | 126 ++++++++++++++++++-------------- backend/authentication/views.py | 24 +----- backend/backend/settings.py | 3 + bin/encrypt-rsa.py | 83 --------------------- 6 files changed, 79 insertions(+), 275 deletions(-) delete mode 100755 bin/encrypt-rsa.py diff --git a/Pipfile b/Pipfile index e343bdd72..5454e07d8 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,5 @@ pandas = "1.5.3" GitPython = "3.1.43" pre-commit = "*" numpy = ">=1,<2" -cryptography = "*" -jwcrypto = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 9e90ad139..48fd073a5 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b28b6f92a95812a821386c5b698252c724d176dc1786d9301e52a554578a2a1c" + "sha256": "15013b0585bd571dcc2e3e58ac7db175ebdd7adf7159c55d5209b897dd931231" }, "pipfile-spec": 6, "requires": {}, @@ -22,64 +22,6 @@ "markers": "python_version >= '3.8'", "version": "==3.8.1" }, - "cffi": { - "hashes": [ - "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", - "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", - "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", - "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", - "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", - "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", - "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", - "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", - "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", - "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", - "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", - "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", - "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", - "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", - "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", - "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", - "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", - "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", - "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", - "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", - "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", - "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", - "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", - "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", - "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", - "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", - "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", - "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", - "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", - "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", - "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", - "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", - "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", - "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", - "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", - "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", - "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", - "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", - "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", - "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", - "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", - "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", - "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", - "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", - "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", - "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", - "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", - "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", - "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", - "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", - "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", - "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" - ], - "markers": "platform_python_implementation != 'PyPy'", - "version": "==1.16.0" - }, "cfgv": { "hashes": [ "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", @@ -88,45 +30,6 @@ "markers": "python_version >= '3.8'", "version": "==3.4.0" }, - "cryptography": { - "hashes": [ - "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad", - "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583", - "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b", - "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c", - "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1", - "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648", - "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949", - "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba", - "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c", - "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9", - "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d", - "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c", - "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e", - "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2", - "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d", - "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7", - "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70", - "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2", - "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7", - "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14", - "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe", - "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e", - "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71", - "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961", - "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7", - "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c", - "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28", - "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842", - "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902", - "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801", - "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a", - "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==42.0.8" - }, "distlib": { "hashes": [ "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784", @@ -258,15 +161,6 @@ "markers": "python_version >= '3.8'", "version": "==2.5.36" }, - "jwcrypto": { - "hashes": [ - "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789", - "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.5.6" - }, "nodeenv": { "hashes": [ "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", @@ -397,14 +291,6 @@ "markers": "python_version >= '3.7'", "version": "==2.9.9" }, - "pycparser": { - "hashes": [ - "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", - "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" - ], - "markers": "python_version >= '3.8'", - "version": "==2.22" - }, "python-dateutil": { "hashes": [ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", diff --git a/backend/authentication/tests.py b/backend/authentication/tests.py index 7bd9c3903..fe0116e91 100644 --- a/backend/authentication/tests.py +++ b/backend/authentication/tests.py @@ -1,12 +1,38 @@ -import json - from django.contrib.auth import get_user_model from django.core import mail from django.test import TestCase from django.urls import reverse +from .models import EcobalyseUser + class DjangoAuthenticationTests(TestCase): + def test_unauthenticated_user_should_not_access_profile(self): + response = self.client.get( + reverse("profile"), + content_type="application/json", + ) + + assert response.status_code == 401 + + def test_authenticated_user_should_access_profile(self): + test_user = EcobalyseUser.objects.get_or_create(email="testuser@test.com")[0] + self.client.force_login(test_user) + response = self.client.get( + reverse("profile"), + content_type="application/json", + ) + assert response.status_code == 200 + + assert response.json() == { + "email": "testuser@test.com", + "first_name": "", + "last_name": "", + "organization": "", + "terms_of_use": False, + "token": str(test_user.token), + } + def test_register_post(self): # invalid mail response = self.client.post( @@ -21,7 +47,7 @@ def test_register_post(self): }, content_type="application/json", ) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 self.assertContains(response, "Saisissez une adresse de courriel valide") # missing first name @@ -37,7 +63,7 @@ def test_register_post(self): }, content_type="application/json", ) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 self.assertContains(response, "Ce champ est obligatoire") # missing last name @@ -53,7 +79,7 @@ def test_register_post(self): }, content_type="application/json", ) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 self.assertContains(response, "Ce champ est obligatoire") # don't accept terms of use @@ -69,7 +95,7 @@ def test_register_post(self): }, content_type="application/json", ) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 self.assertContains(response, "Ce champ est obligatoire") # missing organization is OK @@ -85,18 +111,18 @@ def test_register_post(self): }, content_type="application/json", ) - self.assertEqual(response.status_code, 200) - self.assertEqual(json.loads(response.content).get("success"), True) + assert response.status_code == 200 + assert response.json().get("success") with self.assertLogs(logger="mailauth.backends", level="ERROR") as cm: # wrong json login url response = self.client.get("/accounts/login/invalid-token?next=/") - self.assertEqual(response.status_code, 302) + assert response.status_code == 302 - self.assertIn("BadSignature", " ".join(cm.output)) + assert "BadSignature" in " ".join(cm.output) # right json login url (it's transmitted through reading the outbox) - self.assertEqual(len(mail.outbox), 1) + assert len(mail.outbox) == 1 login_url = "/" + "/".join( [x for x in mail.outbox[0].body.split("\n") if "http" in x][0].split("/")[ 3: @@ -104,8 +130,8 @@ def test_register_post(self): ) response = self.client.get(login_url) # a successful login should redirect to the "next" url - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, "/") + assert response.status_code == 302 + assert response.url == "/" # try to login again response = self.client.post( @@ -116,8 +142,9 @@ def test_register_post(self): }, content_type="application/json", ) - self.assertEqual(response.status_code, 200) - self.assertEqual(json.loads(response.content).get("success"), True) + assert response.status_code == 200 + assert response.json().get("success") + login_url = "/" + "/".join( [x for x in mail.outbox[0].body.split("\n") if "http" in x][0].split("/")[ 3: @@ -125,29 +152,25 @@ def test_register_post(self): ) response = self.client.get(login_url) # a successful login should redirect to the "next" url - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, "/") + assert response.status_code == 302 + assert response.url == "/" + # get json profile response = self.client.get(reverse("profile")) - jsonresp = json.loads(response.content) - self.assertEqual( - list(jsonresp.keys()), - [ - "email", - "first_name", - "last_name", - "organization", - "terms_of_use", - "token", - ], - ) - self.assertEqual( - list(jsonresp.values())[:5], ["test@example.com", "John", "Doe", "", True] - ) + created_user = EcobalyseUser.objects.get(email="test@example.com") + + assert response.json() == { + "email": "test@example.com", + "first_name": "John", + "last_name": "Doe", + "organization": "", + "terms_of_use": True, + "token": str(created_user.token), + } def test_as_admin(self): # create an admin - get_user_model().objects.create_superuser( + super_user = get_user_model().objects.create_superuser( "admin@example.com", terms_of_use=True ) @@ -155,13 +178,14 @@ def test_as_admin(self): response = self.client.post( reverse("login"), { - "email": "admin@example.com", + "email": super_user.email, "next": "/", }, content_type="application/json", ) - self.assertEqual(response.status_code, 200) - self.assertEqual(json.loads(response.content).get("success"), True) + assert response.status_code == 200 + assert response.json().get("success") + login_url = "/" + "/".join( [x for x in mail.outbox[0].body.split("\n") if "http" in x][0].split("/")[ 3: @@ -169,22 +193,18 @@ def test_as_admin(self): ) response = self.client.get(login_url) # a successful login should redirect to the "next" url - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, "/") + assert response.status_code == 302 + assert response.url == "/" + # get json profile response = self.client.get(reverse("profile")) - jsonresp = json.loads(response.content) - self.assertEqual( - list(jsonresp.keys()), - [ - "email", - "first_name", - "last_name", - "organization", - "terms_of_use", - "token", - ], - ) - self.assertEqual( - list(jsonresp.values())[:5], ["admin@example.com", "", "", "", True] - ) + response = self.client.get(reverse("profile")) + + assert response.json() == { + "email": super_user.email, + "first_name": "", + "last_name": "", + "organization": "", + "terms_of_use": True, + "token": str(super_user.token), + } diff --git a/backend/authentication/views.py b/backend/authentication/views.py index 3e1438e03..b3f4035fe 100644 --- a/backend/authentication/views.py +++ b/backend/authentication/views.py @@ -2,19 +2,13 @@ import logging from django.conf import settings -from django.core.exceptions import PermissionDenied -from django.http import JsonResponse, response +from django.http import JsonResponse from django.utils.translation import gettext_lazy as _ -from mailauth.views import ( - LoginTokenView as MailauthLoginTokenView, -) +from django.views.decorators.http import require_http_methods from mailauth.views import ( LoginView as MailauthLoginView, ) -from authentication.models import EcobalyseUser -from django.views.decorators.http import require_http_methods - from .forms import EmailLoginForm, RegistrationForm logger = logging.getLogger(__name__) @@ -117,17 +111,3 @@ def profile(request): "token": u.token, } ) - - -def is_token_valid(token): - return EcobalyseUser.objects.filter(token=token).count() > 0 - - -class EcobalyseLoginTokenView( - MailauthLoginTokenView, -): - def get(self, request, *a, **kwargs): - try: - return super().get(request, *a, **kwargs) - except PermissionDenied: - return response.HttpResponseRedirect("/#/auth/") diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 97fe3349d..0ea1b63eb 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -30,6 +30,9 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = config("DJANGO_DEBUG", cast=bool, default=False) +# SECURITY WARNING: keep the secret key used in production secret! +ENCRYPTION_KEY = config("ENCRYPTION_KEY", "dev_not_so_secret_key") + # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = config("DJANGO_SECRET_KEY", "dev_not_so_secret_key") diff --git a/bin/encrypt-rsa.py b/bin/encrypt-rsa.py deleted file mode 100755 index 689578fbf..000000000 --- a/bin/encrypt-rsa.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/env python -from cryptography.hazmat.primitives.serialization import load_pem_public_key -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import padding -from jwcrypto import jwk -import json - -# Javascript code to generate RSA keys -# -# let keyPair = await window.crypto.subtle.generateKey( -# { -# name: "RSA-OAEP", -# modulusLength: 4096, -# publicExponent: new Uint8Array([1, 0, 1]), -# hash: "SHA-256", -# }, -# true, -# ["encrypt", "decrypt"], -# ); -# -# window.crypto.subtle.exportKey("jwk", keyPair.publicKey).then( -# function(keydata) { -# publicKeyhold = keydata; -# publicKeyJson = JSON.stringify(publicKeyhold); -# console.log(publicKeyJson); -# } -# ); -# {"alg":"RSA-OAEP-256","e":"AQAB","ext":true,"key_ops":["encrypt"],"kty":"RSA","n":"sMZZaPJGTbQRhJyin5SBCbKa0pFMmfbpXea9ES9tH5m87Ec-uFUe7b62Xy72wwTnl5OVNdWK0Ge2Zh_d8vdryo0o7r58twsoHquKEIKdtpdoAPMdDClvherka4bTXrzbDDwnbOMX1PCPmo7hpViJEHL4rtlm0kwkxOhemAd50NFjDIhCqwQ1Um6zunF4bzuA1htq3MKtxeI7p2TR6CGIq1ztgNHRTh-aPR7ItOT9kKwQY1zlDN6mWFr_XZeVfVdd-Acd9GzijF4_-_TF3T95VYH5hCEHAnI9DG7Smz6xh5rfWq_muRMD8llAxbKdD6v6FhU56uXotU0nF4sIHJkoyNifWVHbKEr6CVMK5F0x4COmq5Q4vVq1QABkhL90tWWXtIoFaxfnJVIDeumMIqREw9j5p4b2F-GJnoVaY1CZQfZm9o1rZETI6bCtndXsyqpCgl3vBFEr5DN4AA7gFrAdcQlCve4l5cVCnZBGEKtKehNaXtcspEjogYq9OhJSHMoiK-5zqMHmx2k9s1eBfCq5_VeiuK3oAo3DknkX66gaGOe5M69n1zyJB32N_pVckyP5z2z23KhAtoLBcXKpkiCuFS9tKzejvOmT5EhugQGzoHyEmGhzYlLyG_jTefRhHmX4NL3L3xdQRcI2hC0rcFxaUbDc4CQUyhNVx9ZlpXgluPU"} -# -# Result of the python encryption -# -# var cipherText = "\xa0w\xd8n\x8dx\xdfsH\xc6\x94z\x04\xe6<\xc0\x17)P\xaaW\xa8\xe6L\x98K\xec>\xb5\x0b\x88\xc6\xd8\xa4~\x8a\xb4\xd6r\x8e\xd1/7\x98\x9aGd\x9b\x93\xccp\x10u\xac\xfaI\xd1 @\xe8\x17\xfcO\xaa\x11\xd6I\xb4\x99\xa3\xc6\xce\x1f\x00\x06c\xadJZ\nM\xaaFj\xac\xba\xb6'\xf06\x16F\xc8D\x05\x16F\x10\x88\xa9m\xbf\xb0BO\x91\xd7\x8e\xad\xb9\x13\x8c3\xccp\x83\xc1\xcd\x05\xab\xb1[\xeb\xd8\x07\x82\xack\xe9\xf9\xf2\x0f\xef\xda\xd6\xb2d\xd5\x0e\xad\xa3\xbc\x9e\x94\x9d\x19\xc4\xf0^\xe7(`n\xcai*\x0fD\x06\xf1\xcePm\x19\xdf\x90\xd9]\xd2\xc479\n\x02\xa4'\x02\x16\xba\xdd\xd0\x1e\xec\x87N\xb2|\xf6o\xbc\x9fu\xa2H\x04\xd6\xd8= Date: Thu, 4 Jul 2024 16:29:20 +0200 Subject: [PATCH 17/30] fix: refactor and pass token when retrieving processes. --- src/Data/Session.elm | 40 ++++++++++++++++------------------------ src/Page/Auth.elm | 9 ++++++--- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/Data/Session.elm b/src/Data/Session.elm index 436ab18f2..7881cc64d 100644 --- a/src/Data/Session.elm +++ b/src/Data/Session.elm @@ -39,7 +39,6 @@ import Json.Encode as Encode import Request.Version exposing (Version) import Set exposing (Set) import Static.Db as StaticDb exposing (Db) -import Task type alias Session = @@ -323,34 +322,27 @@ authenticated ({ store } as session) user { textileProcessesJson, foodProcessesJ type alias AllProcessesJson = - { textileProcessesJson : String, foodProcessesJson : String } - - -login : (Result String AllProcessesJson -> msg) -> Cmd msg -login event = - Task.attempt event - (getProcesses "processes/processes.json") + { textileProcessesJson : String + , foodProcessesJson : String + } -getProcesses : String -> Task.Task String AllProcessesJson -getProcesses url = - Http.task +login : Session -> (Result Http.Error AllProcessesJson -> msg) -> Cmd msg +login { store } event = + Http.request { method = "GET" - , headers = [] - , url = url + , url = "processes/processes.json" + , headers = + case store.auth of + NotAuthenticated -> + [] + + Authenticated { token } _ _ -> + [ Http.header "token" token ] , body = Http.emptyBody - , resolver = - Http.stringResolver - (\response -> - case response of - Http.GoodStatus_ _ stringBody -> - Decode.decodeString decodeAllProcessesJson stringBody - |> Result.mapError Decode.errorToString - - _ -> - Err "Couldn't get the processes" - ) + , expect = Http.expectJson event decodeAllProcessesJson , timeout = Nothing + , tracker = Nothing } diff --git a/src/Page/Auth.elm b/src/Page/Auth.elm index 690fe18db..5bfe71b9c 100644 --- a/src/Page/Auth.elm +++ b/src/Page/Auth.elm @@ -33,7 +33,7 @@ type alias Model = type Msg = AskForRegistration - | Authenticated User (Result String Session.AllProcessesJson) + | Authenticated User (Result Http.Error Session.AllProcessesJson) | ChangeAction Action | GotUserInfo (Result Http.Error User) | LoggedOut @@ -130,7 +130,10 @@ update session msg model = Authenticated _ (Err error) -> ( model - , session |> Session.notifyError "Impossible de charger les impacts lors de la connexion" error + , session + |> Session.notifyError + "Impossible de charger les impacts lors de la connexion" + (RequestCommon.errorToString error) , Cmd.none ) @@ -143,7 +146,7 @@ update session msg model = GotUserInfo (Ok user) -> ( { model | user = user } , session - , Session.login (Authenticated user) + , Session.login session (Authenticated user) ) GotUserInfo (Err err) -> From b11b4499f0515f1f369e81dd691c88b81ec35921 Mon Sep 17 00:00:00 2001 From: Nicolas Perriault Date: Thu, 4 Jul 2024 16:45:07 +0200 Subject: [PATCH 18/30] Decode processes from JSON value. --- src/Data/Session.elm | 52 +++++++++++++++++++------------------------- src/Page/Auth.elm | 2 +- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/Data/Session.elm b/src/Data/Session.elm index 7881cc64d..0b7c345de 100644 --- a/src/Data/Session.elm +++ b/src/Data/Session.elm @@ -1,5 +1,5 @@ module Data.Session exposing - ( AllProcessesJson + ( AllProcesses , Auth(..) , Notification(..) , Session @@ -225,11 +225,11 @@ decodeAuth = |> JDP.required "foodProcesses" (FoodProcess.decodeList Impact.decodeImpacts) -decodeAllProcessesJson : Decoder AllProcessesJson -decodeAllProcessesJson = - Decode.succeed AllProcessesJson - |> JDP.required "textileProcesses" Decode.string - |> JDP.required "foodProcesses" Decode.string +decodeAllProcesses : Decoder Impact.Impacts -> Decoder AllProcesses +decodeAllProcesses impactsDecoder = + Decode.succeed AllProcesses + |> JDP.required "textileProcesses" (TextileProcess.decodeList impactsDecoder) + |> JDP.required "foodProcesses" (FoodProcess.decodeList impactsDecoder) encodeStore : Store -> Encode.Value @@ -297,37 +297,29 @@ updateStore update session = { session | store = update session.store } -authenticated : Session -> User -> AllProcessesJson -> Session -authenticated ({ store } as session) user { textileProcessesJson, foodProcessesJson } = +authenticated : Session -> User -> AllProcesses -> Session +authenticated ({ db, store } as session) user { textileProcesses, foodProcesses } = let - originalProcesses = - StaticDb.processes - - newProcesses = - { originalProcesses - | foodProcesses = foodProcessesJson - , textileProcesses = textileProcessesJson - } + { food, textile } = + db in - case StaticDb.db newProcesses of - Ok db -> - { session - | db = db - , store = { store | auth = Authenticated user db.textile.processes db.food.processes } + { session + | db = + { db + | food = { food | processes = foodProcesses } + , textile = { textile | processes = textileProcesses } } - - Err err -> - session - |> notifyError "Impossible de recharger la db avec les nouveaux procédés" err + , store = { store | auth = Authenticated user textileProcesses foodProcesses } + } -type alias AllProcessesJson = - { textileProcessesJson : String - , foodProcessesJson : String +type alias AllProcesses = + { textileProcesses : List TextileProcess.Process + , foodProcesses : List FoodProcess.Process } -login : Session -> (Result Http.Error AllProcessesJson -> msg) -> Cmd msg +login : Session -> (Result Http.Error AllProcesses -> msg) -> Cmd msg login { store } event = Http.request { method = "GET" @@ -340,7 +332,7 @@ login { store } event = Authenticated { token } _ _ -> [ Http.header "token" token ] , body = Http.emptyBody - , expect = Http.expectJson event decodeAllProcessesJson + , expect = Http.expectJson event (decodeAllProcesses Impact.decodeImpacts) , timeout = Nothing , tracker = Nothing } diff --git a/src/Page/Auth.elm b/src/Page/Auth.elm index 5bfe71b9c..e9bda49b3 100644 --- a/src/Page/Auth.elm +++ b/src/Page/Auth.elm @@ -33,7 +33,7 @@ type alias Model = type Msg = AskForRegistration - | Authenticated User (Result Http.Error Session.AllProcessesJson) + | Authenticated User (Result Http.Error Session.AllProcesses) | ChangeAction Action | GotUserInfo (Result Http.Error User) | LoggedOut From b14451d6fc13fdf7ac75028cd362f1e08fdffb9a Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Thu, 4 Jul 2024 16:57:15 +0200 Subject: [PATCH 19/30] fix: check token and serve appropriate files --- backend/authentication/views.py | 16 ++++++++++- backend/backend/urls.py | 2 ++ backend/internal/urls.py | 5 ++++ backend/internal/views.py | 17 ++++++++++++ server.js | 47 ++++++++++++++++++++++++--------- 5 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 backend/internal/urls.py create mode 100644 backend/internal/views.py diff --git a/backend/authentication/views.py b/backend/authentication/views.py index b3f4035fe..59500dba4 100644 --- a/backend/authentication/views.py +++ b/backend/authentication/views.py @@ -2,9 +2,13 @@ import logging from django.conf import settings -from django.http import JsonResponse +from django.core.exceptions import PermissionDenied +from django.http import JsonResponse, response from django.utils.translation import gettext_lazy as _ from django.views.decorators.http import require_http_methods +from mailauth.views import ( + LoginTokenView as MailauthLoginTokenView, +) from mailauth.views import ( LoginView as MailauthLoginView, ) @@ -111,3 +115,13 @@ def profile(request): "token": u.token, } ) + + +class EcobalyseLoginTokenView( + MailauthLoginTokenView, +): + def get(self, request, *a, **kwargs): + try: + return super().get(request, *a, **kwargs) + except PermissionDenied: + return response.HttpResponseRedirect("/#/auth/") diff --git a/backend/backend/urls.py b/backend/backend/urls.py index 140ed8fa9..b72f205e8 100644 --- a/backend/backend/urls.py +++ b/backend/backend/urls.py @@ -25,6 +25,8 @@ urlpatterns = [ path("admin/", admin_site.urls), path("accounts/", include("authentication.urls")), + # Localhost only calls, url not mapped to the nginx proxy + path("internal/", include("internal.urls")), ] if settings.DEBUG: diff --git a/backend/internal/urls.py b/backend/internal/urls.py new file mode 100644 index 000000000..b21f4b78c --- /dev/null +++ b/backend/internal/urls.py @@ -0,0 +1,5 @@ +from django.urls import path + +from .views import check_token + +urlpatterns = [path("check_token", check_token)] diff --git a/backend/internal/views.py b/backend/internal/views.py new file mode 100644 index 000000000..be6c13266 --- /dev/null +++ b/backend/internal/views.py @@ -0,0 +1,17 @@ +from authentication.models import EcobalyseUser +from django.http import JsonResponse + + +def is_token_valid(token): + return EcobalyseUser.objects.filter(token=token).count() > 0 + + +def check_token(request): + token = request.headers.get("token") + if is_token_valid(token): + return JsonResponse({}) + else: + return JsonResponse( + {"error": "This token isn't valid."}, + status=401, + ) diff --git a/server.js b/server.js index 96291ea87..02a02b721 100644 --- a/server.js +++ b/server.js @@ -12,8 +12,10 @@ const { encrypt } = require("./lib/crypto"); const app = express(); // web app const api = express(); // api app -const host = "0.0.0.0"; -const express_port = 8001; +const expressHost = "0.0.0.0"; +const expressPort = 8001; +const djangoHost = "127.0.0.1"; +const djangoPort = 8002; // Env vars const { SENTRY_DSN, MATOMO_HOST, MATOMO_SITE_ID, MATOMO_TOKEN } = process.env; @@ -92,19 +94,38 @@ const apiTracker = lib.setupTracker(openApiContents); // Detailed processes files -const textileFile = "public/data/textile/processes_impacts.json"; -const foodFile = "public/data/food/processes_impacts.json"; +const textileImpactsFile = "public/data/textile/processes_impacts.json"; +const foodImpactsFile = "public/data/food/processes_impacts.json"; +const textileFile = "public/data/textile/processes.json"; +const foodFile = "public/data/food/processes.json"; + +const processesImpacts = { + foodProcesses: JSON.parse(fs.readFileSync(foodImpactsFile, "utf8")), + textileProcesses: JSON.parse(fs.readFileSync(textileImpactsFile, "utf8")), +}; const processes = { - foodProcesses: fs.readFileSync(foodFile, "utf8"), - textileProcesses: fs.readFileSync(textileFile, "utf8"), + foodProcesses: JSON.parse(fs.readFileSync(foodFile, "utf8")), + textileProcesses: JSON.parse(fs.readFileSync(textileFile, "utf8")), }; -const { ENCRYPTION_KEY } = process.env; -const encryptedProcesses = encrypt(JSON.stringify(processes), ENCRYPTION_KEY); +const getProcesses = async (token) => { + let isTokenValid = false; + if (token) { + const checkTokenUrl = `http://${djangoHost}:${djangoPort}/internal/check_token`; + const tokenRes = await fetch(checkTokenUrl, { headers: { token } }); + isTokenValid = tokenRes.status == 200; + } + + if (isTokenValid) { + return processesImpacts; + } else { + return processes; + } +}; -app.get("/processes/processes.json", async (_, res) => { - return res.status(200).send(encryptedProcesses); +app.get("/processes/processes.json", async (req, res) => { + return res.status(200).send(await getProcesses(req.headers.token)); }); const elmApp = Elm.Server.init(); @@ -127,6 +148,8 @@ api.get(/^\/simulator(.*)$/, ({ url }, res) => res.redirect(`/api/textile${clean // Note: Text/JSON request body parser (JSON is decoded in Elm) api.all(/(.*)/, bodyParser.json(), async (req, res) => { + const processes = await getProcesses(req.headers.token); + elmApp.ports.input.send({ method: req.method, url: req.url, @@ -148,8 +171,8 @@ if (SENTRY_DSN) { app.use(Sentry.Handlers.errorHandler()); } -const server = app.listen(express_port, host, () => { - console.log(`Server listening at http://${host}:${express_port}`); +const server = app.listen(expressPort, expressHost, () => { + console.log(`Server listening at http://${expressHost}:${expressPort}`); }); module.exports = server; From 0d465e2bb49df8c2f9bfba120657b2138789130e Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Thu, 4 Jul 2024 17:04:38 +0200 Subject: [PATCH 20/30] fix: always provide full impacts in test mode --- .env.sample | 1 - backend/backend/settings.py | 3 --- server.js | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.env.sample b/.env.sample index a4c943173..f525238b0 100644 --- a/.env.sample +++ b/.env.sample @@ -3,7 +3,6 @@ BACKEND_ADMINS=foo@bar.baz,baz@bar.foo DEFAULT_FROM_EMAIL=bar@foo.baz DJANGO_DEBUG=True DJANGO_SECRET_KEY=please-change-this -DJANGO_BYPASS_AUTH=False ENCRYPTION_KEY=my_secret_encryptionkey_changeit EMAIL_HOST_PASSWORD=please-change-this EMAIL_HOST_USER=please-change-this diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 0ea1b63eb..791c964e1 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -36,9 +36,6 @@ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = config("DJANGO_SECRET_KEY", "dev_not_so_secret_key") -# SECURITY WARNING: don't bypass auth in production -BYPASS_AUTH = config("DJANGO_BYPASS_AUTH", cast=bool, default=False) - ALLOWED_HOSTS = config( "ALLOWED_HOSTS", f"{HOSTNAME},localhost,127.0.0.1", diff --git a/server.js b/server.js index 02a02b721..7d395de33 100644 --- a/server.js +++ b/server.js @@ -117,7 +117,7 @@ const getProcesses = async (token) => { isTokenValid = tokenRes.status == 200; } - if (isTokenValid) { + if (isTokenValid || process.env.NODE_ENV === "test") { return processesImpacts; } else { return processes; From 2ab424d96b3a851578a34e6e579c43a73249b710 Mon Sep 17 00:00:00 2001 From: Nicolas Perriault Date: Thu, 4 Jul 2024 17:20:48 +0200 Subject: [PATCH 21/30] Revert "Decode processes from JSON value." This reverts commit b11b4499f0515f1f369e81dd691c88b81ec35921. --- src/Data/Session.elm | 52 +++++++++++++++++++++++++------------------- src/Page/Auth.elm | 2 +- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/Data/Session.elm b/src/Data/Session.elm index 0b7c345de..7881cc64d 100644 --- a/src/Data/Session.elm +++ b/src/Data/Session.elm @@ -1,5 +1,5 @@ module Data.Session exposing - ( AllProcesses + ( AllProcessesJson , Auth(..) , Notification(..) , Session @@ -225,11 +225,11 @@ decodeAuth = |> JDP.required "foodProcesses" (FoodProcess.decodeList Impact.decodeImpacts) -decodeAllProcesses : Decoder Impact.Impacts -> Decoder AllProcesses -decodeAllProcesses impactsDecoder = - Decode.succeed AllProcesses - |> JDP.required "textileProcesses" (TextileProcess.decodeList impactsDecoder) - |> JDP.required "foodProcesses" (FoodProcess.decodeList impactsDecoder) +decodeAllProcessesJson : Decoder AllProcessesJson +decodeAllProcessesJson = + Decode.succeed AllProcessesJson + |> JDP.required "textileProcesses" Decode.string + |> JDP.required "foodProcesses" Decode.string encodeStore : Store -> Encode.Value @@ -297,29 +297,37 @@ updateStore update session = { session | store = update session.store } -authenticated : Session -> User -> AllProcesses -> Session -authenticated ({ db, store } as session) user { textileProcesses, foodProcesses } = +authenticated : Session -> User -> AllProcessesJson -> Session +authenticated ({ store } as session) user { textileProcessesJson, foodProcessesJson } = let - { food, textile } = - db + originalProcesses = + StaticDb.processes + + newProcesses = + { originalProcesses + | foodProcesses = foodProcessesJson + , textileProcesses = textileProcessesJson + } in - { session - | db = - { db - | food = { food | processes = foodProcesses } - , textile = { textile | processes = textileProcesses } + case StaticDb.db newProcesses of + Ok db -> + { session + | db = db + , store = { store | auth = Authenticated user db.textile.processes db.food.processes } } - , store = { store | auth = Authenticated user textileProcesses foodProcesses } - } + + Err err -> + session + |> notifyError "Impossible de recharger la db avec les nouveaux procédés" err -type alias AllProcesses = - { textileProcesses : List TextileProcess.Process - , foodProcesses : List FoodProcess.Process +type alias AllProcessesJson = + { textileProcessesJson : String + , foodProcessesJson : String } -login : Session -> (Result Http.Error AllProcesses -> msg) -> Cmd msg +login : Session -> (Result Http.Error AllProcessesJson -> msg) -> Cmd msg login { store } event = Http.request { method = "GET" @@ -332,7 +340,7 @@ login { store } event = Authenticated { token } _ _ -> [ Http.header "token" token ] , body = Http.emptyBody - , expect = Http.expectJson event (decodeAllProcesses Impact.decodeImpacts) + , expect = Http.expectJson event decodeAllProcessesJson , timeout = Nothing , tracker = Nothing } diff --git a/src/Page/Auth.elm b/src/Page/Auth.elm index e9bda49b3..5bfe71b9c 100644 --- a/src/Page/Auth.elm +++ b/src/Page/Auth.elm @@ -33,7 +33,7 @@ type alias Model = type Msg = AskForRegistration - | Authenticated User (Result Http.Error Session.AllProcesses) + | Authenticated User (Result Http.Error Session.AllProcessesJson) | ChangeAction Action | GotUserInfo (Result Http.Error User) | LoggedOut From cd867fc55737dd86cb98079263339233c29f1f17 Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Thu, 4 Jul 2024 17:29:58 +0200 Subject: [PATCH 22/30] fix: remove unused variable --- .github/workflows/score_history.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/score_history.yml b/.github/workflows/score_history.yml index f37c326d1..f51689622 100644 --- a/.github/workflows/score_history.yml +++ b/.github/workflows/score_history.yml @@ -90,7 +90,6 @@ jobs: GITHUB_REF_NAME: ${{ github.ref_name }} LAST_COMMIT_HASH: ${{ github.sha }} SCALINGO_POSTGRESQL_SCORE_URL: ${{ secrets.SCALINGO_POSTGRESQL_TUNNEL_SCORE_URL }} - DJANGO_BYPASS_AUTH: True NODE_ENV: "test" SCALINGO_REGION: ${{ secrets.SCALINGO_REGION }} SCALINGO_APP: ecobalyse From 3e63c29190e56445196e79acca7fd87b49019ff7 Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Thu, 4 Jul 2024 17:30:13 +0200 Subject: [PATCH 23/30] fix: pass strings to Elm --- server.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server.js b/server.js index 7d395de33..f031dff9b 100644 --- a/server.js +++ b/server.js @@ -100,13 +100,13 @@ const textileFile = "public/data/textile/processes.json"; const foodFile = "public/data/food/processes.json"; const processesImpacts = { - foodProcesses: JSON.parse(fs.readFileSync(foodImpactsFile, "utf8")), - textileProcesses: JSON.parse(fs.readFileSync(textileImpactsFile, "utf8")), + foodProcesses: fs.readFileSync(foodImpactsFile, "utf8"), + textileProcesses: fs.readFileSync(textileImpactsFile, "utf8"), }; const processes = { - foodProcesses: JSON.parse(fs.readFileSync(foodFile, "utf8")), - textileProcesses: JSON.parse(fs.readFileSync(textileFile, "utf8")), + foodProcesses: fs.readFileSync(foodFile, "utf8"), + textileProcesses: fs.readFileSync(textileFile, "utf8"), }; const getProcesses = async (token) => { From 58b7b19eefdc1162efc2359609422070917985f8 Mon Sep 17 00:00:00 2001 From: Nicolas Perriault Date: Mon, 8 Jul 2024 17:21:36 +0200 Subject: [PATCH 24/30] fix: retrieve detailed processes after login. --- src/Data/Session.elm | 12 +++--------- src/Page/Auth.elm | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Data/Session.elm b/src/Data/Session.elm index 7881cc64d..f10869581 100644 --- a/src/Data/Session.elm +++ b/src/Data/Session.elm @@ -327,18 +327,12 @@ type alias AllProcessesJson = } -login : Session -> (Result Http.Error AllProcessesJson -> msg) -> Cmd msg -login { store } event = +login : String -> (Result Http.Error AllProcessesJson -> msg) -> Cmd msg +login token event = Http.request { method = "GET" , url = "processes/processes.json" - , headers = - case store.auth of - NotAuthenticated -> - [] - - Authenticated { token } _ _ -> - [ Http.header "token" token ] + , headers = [ Http.header "token" token ] , body = Http.emptyBody , expect = Http.expectJson event decodeAllProcessesJson , timeout = Nothing diff --git a/src/Page/Auth.elm b/src/Page/Auth.elm index 5bfe71b9c..9a30bf3ff 100644 --- a/src/Page/Auth.elm +++ b/src/Page/Auth.elm @@ -146,7 +146,7 @@ update session msg model = GotUserInfo (Ok user) -> ( { model | user = user } , session - , Session.login session (Authenticated user) + , Session.login user.token (Authenticated user) ) GotUserInfo (Err err) -> From 73579925614bc3dd07843d05421b5bf54aa95989 Mon Sep 17 00:00:00 2001 From: Nicolas Perriault Date: Mon, 8 Jul 2024 17:39:45 +0200 Subject: [PATCH 25/30] chore: extract auth request code to Request.Auth. --- src/Data/Session.elm | 22 ---------------------- src/Page/Auth.elm | 26 +++++++------------------ src/Request/Auth.elm | 45 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 41 deletions(-) create mode 100644 src/Request/Auth.elm diff --git a/src/Data/Session.elm b/src/Data/Session.elm index f10869581..4c7060d7c 100644 --- a/src/Data/Session.elm +++ b/src/Data/Session.elm @@ -11,7 +11,6 @@ module Data.Session exposing , deserializeStore , getUser , isAuthenticated - , login , logout , notifyError , notifyInfo @@ -32,7 +31,6 @@ import Data.Impact as Impact import Data.Textile.Process as TextileProcess import Data.Textile.Query as TextileQuery import Data.User as User exposing (User) -import Http import Json.Decode as Decode exposing (Decoder) import Json.Decode.Pipeline as JDP import Json.Encode as Encode @@ -225,13 +223,6 @@ decodeAuth = |> JDP.required "foodProcesses" (FoodProcess.decodeList Impact.decodeImpacts) -decodeAllProcessesJson : Decoder AllProcessesJson -decodeAllProcessesJson = - Decode.succeed AllProcessesJson - |> JDP.required "textileProcesses" Decode.string - |> JDP.required "foodProcesses" Decode.string - - encodeStore : Store -> Encode.Value encodeStore store = Encode.object @@ -327,19 +318,6 @@ type alias AllProcessesJson = } -login : String -> (Result Http.Error AllProcessesJson -> msg) -> Cmd msg -login token event = - Http.request - { method = "GET" - , url = "processes/processes.json" - , headers = [ Http.header "token" token ] - , body = Http.emptyBody - , expect = Http.expectJson event decodeAllProcessesJson - , timeout = Nothing - , tracker = Nothing - } - - logout : Session -> Session logout ({ store } as session) = case StaticDb.db StaticDb.processes of diff --git a/src/Page/Auth.elm b/src/Page/Auth.elm index 9a30bf3ff..78b0c63d4 100644 --- a/src/Page/Auth.elm +++ b/src/Page/Auth.elm @@ -8,7 +8,7 @@ module Page.Auth exposing import Data.Env as Env import Data.Session as Session exposing (Session) -import Data.User as User exposing (User) +import Data.User exposing (User) import Dict exposing (Dict) import Html exposing (..) import Html.Attributes exposing (..) @@ -16,6 +16,7 @@ import Html.Events exposing (..) import Http import Json.Decode as Decode exposing (Decoder) import Json.Encode as Encode +import Request.Auth import Request.Common as RequestCommon import Route import Views.Alert as Alert @@ -35,7 +36,7 @@ type Msg = AskForRegistration | Authenticated User (Result Http.Error Session.AllProcessesJson) | ChangeAction Action - | GotUserInfo (Result Http.Error User) + | GotProfile (Result Http.Error User) | LoggedOut | Login | Logout @@ -65,7 +66,7 @@ init : Session -> { authenticated : Bool } -> ( Model, Session, Cmd Msg ) init session data = ( emptyModel data , session - , getUserInfo GotUserInfo + , Request.Auth.user GotProfile ) @@ -143,13 +144,13 @@ update session msg model = , Cmd.none ) - GotUserInfo (Ok user) -> + GotProfile (Ok user) -> ( { model | user = user } , session - , Session.login user.token (Authenticated user) + , Request.Auth.processes user.token (Authenticated user) ) - GotUserInfo (Err err) -> + GotProfile (Err err) -> ( { model | authenticated = False } , if model.authenticated then -- We're here following a click on a login link in an email. If we failed, notify the user. @@ -581,19 +582,6 @@ viewFormErrors maybeResponse = ---- helpers -getUserInfo : (Result Http.Error User -> Msg) -> Cmd Msg -getUserInfo event = - Http.riskyRequest - { method = "GET" - , headers = [] - , url = "/accounts/profile/" - , body = Http.emptyBody - , expect = Http.expectJson event User.decode - , timeout = Nothing - , tracker = Nothing - } - - logout : Cmd Msg logout = Http.riskyRequest diff --git a/src/Request/Auth.elm b/src/Request/Auth.elm new file mode 100644 index 000000000..a78fa35dd --- /dev/null +++ b/src/Request/Auth.elm @@ -0,0 +1,45 @@ +module Request.Auth exposing (processes, user) + +import Data.User as User exposing (User) +import Http +import Json.Decode as Decode exposing (Decoder) +import Json.Decode.Pipeline as JDP + + +type alias AllProcessesJson = + { textileProcessesJson : String + , foodProcessesJson : String + } + + +decodeAllProcessesJson : Decoder AllProcessesJson +decodeAllProcessesJson = + Decode.succeed AllProcessesJson + |> JDP.required "textileProcesses" Decode.string + |> JDP.required "foodProcesses" Decode.string + + +processes : String -> (Result Http.Error AllProcessesJson -> msg) -> Cmd msg +processes token event = + Http.request + { method = "GET" + , url = "processes/processes.json" + , headers = [ Http.header "token" token ] + , body = Http.emptyBody + , expect = Http.expectJson event decodeAllProcessesJson + , timeout = Nothing + , tracker = Nothing + } + + +user : (Result Http.Error User -> msg) -> Cmd msg +user event = + Http.riskyRequest + { method = "GET" + , headers = [] + , url = "/accounts/profile/" + , body = Http.emptyBody + , expect = Http.expectJson event User.decode + , timeout = Nothing + , tracker = Nothing + } From 696fd2927e6de5ad6141e1d39cbede96030a463c Mon Sep 17 00:00:00 2001 From: Nicolas Perriault Date: Mon, 8 Jul 2024 17:46:05 +0200 Subject: [PATCH 26/30] chore: Move AllProcessesJson to Static.Db. --- src/Data/Session.elm | 11 ++--------- src/Page/Auth.elm | 3 ++- src/Request/Auth.elm | 7 +------ src/Static/Db.elm | 9 ++++++++- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/Data/Session.elm b/src/Data/Session.elm index 4c7060d7c..135e310c8 100644 --- a/src/Data/Session.elm +++ b/src/Data/Session.elm @@ -1,6 +1,5 @@ module Data.Session exposing - ( AllProcessesJson - , Auth(..) + ( Auth(..) , Notification(..) , Session , Store @@ -36,7 +35,7 @@ import Json.Decode.Pipeline as JDP import Json.Encode as Encode import Request.Version exposing (Version) import Set exposing (Set) -import Static.Db as StaticDb exposing (Db) +import Static.Db as StaticDb exposing (AllProcessesJson, Db) type alias Session = @@ -312,12 +311,6 @@ authenticated ({ store } as session) user { textileProcessesJson, foodProcessesJ |> notifyError "Impossible de recharger la db avec les nouveaux procédés" err -type alias AllProcessesJson = - { textileProcessesJson : String - , foodProcessesJson : String - } - - logout : Session -> Session logout ({ store } as session) = case StaticDb.db StaticDb.processes of diff --git a/src/Page/Auth.elm b/src/Page/Auth.elm index 78b0c63d4..3df9a655d 100644 --- a/src/Page/Auth.elm +++ b/src/Page/Auth.elm @@ -19,6 +19,7 @@ import Json.Encode as Encode import Request.Auth import Request.Common as RequestCommon import Route +import Static.Db exposing (AllProcessesJson) import Views.Alert as Alert import Views.Container as Container import Views.Markdown as Markdown @@ -34,7 +35,7 @@ type alias Model = type Msg = AskForRegistration - | Authenticated User (Result Http.Error Session.AllProcessesJson) + | Authenticated User (Result Http.Error AllProcessesJson) | ChangeAction Action | GotProfile (Result Http.Error User) | LoggedOut diff --git a/src/Request/Auth.elm b/src/Request/Auth.elm index a78fa35dd..6d71c6846 100644 --- a/src/Request/Auth.elm +++ b/src/Request/Auth.elm @@ -4,12 +4,7 @@ import Data.User as User exposing (User) import Http import Json.Decode as Decode exposing (Decoder) import Json.Decode.Pipeline as JDP - - -type alias AllProcessesJson = - { textileProcessesJson : String - , foodProcessesJson : String - } +import Static.Db exposing (AllProcessesJson) decodeAllProcessesJson : Decoder AllProcessesJson diff --git a/src/Static/Db.elm b/src/Static/Db.elm index 4eb59303e..393aa9bc1 100644 --- a/src/Static/Db.elm +++ b/src/Static/Db.elm @@ -1,5 +1,6 @@ module Static.Db exposing - ( Db + ( AllProcessesJson + , Db , db , processes , updateEcotoxWeighting @@ -28,6 +29,12 @@ type alias Db = } +type alias AllProcessesJson = + { textileProcessesJson : String + , foodProcessesJson : String + } + + db : StaticJson.Processes -> Result String Db db procs = StaticJson.db procs From 121f560fb2062c1c4bcb344629e83a5bf8729f8f Mon Sep 17 00:00:00 2001 From: Nicolas Perriault Date: Mon, 8 Jul 2024 17:51:44 +0200 Subject: [PATCH 27/30] chore: move user stuff to User.module. --- src/Data/User.elm | 30 ++++++++++++++++++++++++++++++ src/Page/Auth.elm | 35 +++++------------------------------ 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/Data/User.elm b/src/Data/User.elm index 70ea5c81d..60411ceb8 100644 --- a/src/Data/User.elm +++ b/src/Data/User.elm @@ -2,6 +2,8 @@ module Data.User exposing ( User , decode , encode + , encodeForm + , form ) import Json.Decode as Decode exposing (Decoder) @@ -19,6 +21,10 @@ type alias User = } +type alias Form a = + { a | next : String } + + decode : Decoder User decode = Decode.succeed User @@ -40,3 +46,27 @@ encode user = , ( "terms_of_use", Encode.bool user.cgu ) , ( "token", Encode.string user.token ) ] + + +encodeForm : Form User -> Encode.Value +encodeForm user = + Encode.object + [ ( "email", Encode.string user.email ) + , ( "first_name", Encode.string user.firstname ) + , ( "last_name", Encode.string user.lastname ) + , ( "organization", Encode.string user.company ) + , ( "terms_of_use", Encode.bool user.cgu ) + , ( "next", Encode.string user.next ) + ] + + +form : User -> Form User +form user = + { email = user.email + , firstname = user.firstname + , lastname = user.lastname + , company = user.company + , cgu = user.cgu + , token = "" + , next = "/#/auth/authenticated" + } diff --git a/src/Page/Auth.elm b/src/Page/Auth.elm index 3df9a655d..b3e25b0ee 100644 --- a/src/Page/Auth.elm +++ b/src/Page/Auth.elm @@ -8,7 +8,7 @@ module Page.Auth exposing import Data.Env as Env import Data.Session as Session exposing (Session) -import Data.User exposing (User) +import Data.User as User exposing (User) import Dict exposing (Dict) import Html exposing (..) import Html.Attributes exposing (..) @@ -54,10 +54,6 @@ type alias Errors = Dict String String -type alias Form a = - { a | next : String } - - type Response = Success String | Error String (Maybe Errors) @@ -83,18 +79,6 @@ init session data = -- - once the link in the email received is clicked, may not go through the login flow -formFromUser : User -> Form User -formFromUser user = - { email = user.email - , firstname = user.firstname - , lastname = user.lastname - , company = user.company - , cgu = user.cgu - , token = "" - , next = "/#/auth/authenticated" - } - - emptyModel : { authenticated : Bool } -> Model emptyModel { authenticated } = { user = @@ -119,7 +103,10 @@ update session msg model = , session , Http.post { url = "/accounts/register/" - , body = Http.jsonBody (encodeUserForm (formFromUser model.user)) + , body = + User.form model.user + |> User.encodeForm + |> Http.jsonBody , expect = Http.expectJson TokenEmailSent decodeResponse } ) @@ -648,15 +635,3 @@ decodeResponse = encodeEmail : String -> Encode.Value encodeEmail email = Encode.object [ ( "email", Encode.string email ) ] - - -encodeUserForm : Form User -> Encode.Value -encodeUserForm user = - Encode.object - [ ( "email", Encode.string user.email ) - , ( "first_name", Encode.string user.firstname ) - , ( "last_name", Encode.string user.lastname ) - , ( "organization", Encode.string user.company ) - , ( "terms_of_use", Encode.bool user.cgu ) - , ( "next", Encode.string user.next ) - ] From 9149a5ab8246a5a7fd579690039e5618136a3cba Mon Sep 17 00:00:00 2001 From: Nicolas Perriault Date: Mon, 8 Jul 2024 19:43:43 +0200 Subject: [PATCH 28/30] chore: Move static Json related stuff to Static.Json. --- src/Data/Session.elm | 11 ++++++----- src/Page/Auth.elm | 4 ++-- src/Request/Auth.elm | 16 ++++------------ src/Server/Request.elm | 2 +- src/Static/Db.elm | 25 ++++++++++++++----------- src/Static/Json.elm-template | 16 ++++++++++------ 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/Data/Session.elm b/src/Data/Session.elm index 135e310c8..852b5631a 100644 --- a/src/Data/Session.elm +++ b/src/Data/Session.elm @@ -35,7 +35,8 @@ import Json.Decode.Pipeline as JDP import Json.Encode as Encode import Request.Version exposing (Version) import Set exposing (Set) -import Static.Db as StaticDb exposing (AllProcessesJson, Db) +import Static.Db as StaticDb exposing (Db) +import Static.Json exposing (RawJsonProcesses) type alias Session = @@ -287,16 +288,16 @@ updateStore update session = { session | store = update session.store } -authenticated : Session -> User -> AllProcessesJson -> Session -authenticated ({ store } as session) user { textileProcessesJson, foodProcessesJson } = +authenticated : Session -> User -> RawJsonProcesses -> Session +authenticated ({ store } as session) user { textileProcesses, foodProcesses } = let originalProcesses = StaticDb.processes newProcesses = { originalProcesses - | foodProcesses = foodProcessesJson - , textileProcesses = textileProcessesJson + | foodProcesses = foodProcesses + , textileProcesses = textileProcesses } in case StaticDb.db newProcesses of diff --git a/src/Page/Auth.elm b/src/Page/Auth.elm index b3e25b0ee..ee03b777d 100644 --- a/src/Page/Auth.elm +++ b/src/Page/Auth.elm @@ -19,7 +19,7 @@ import Json.Encode as Encode import Request.Auth import Request.Common as RequestCommon import Route -import Static.Db exposing (AllProcessesJson) +import Static.Json exposing (RawJsonProcesses) import Views.Alert as Alert import Views.Container as Container import Views.Markdown as Markdown @@ -35,7 +35,7 @@ type alias Model = type Msg = AskForRegistration - | Authenticated User (Result Http.Error AllProcessesJson) + | Authenticated User (Result Http.Error RawJsonProcesses) | ChangeAction Action | GotProfile (Result Http.Error User) | LoggedOut diff --git a/src/Request/Auth.elm b/src/Request/Auth.elm index 6d71c6846..87af04bcd 100644 --- a/src/Request/Auth.elm +++ b/src/Request/Auth.elm @@ -2,26 +2,18 @@ module Request.Auth exposing (processes, user) import Data.User as User exposing (User) import Http -import Json.Decode as Decode exposing (Decoder) -import Json.Decode.Pipeline as JDP -import Static.Db exposing (AllProcessesJson) +import Static.Db as Db +import Static.Json exposing (RawJsonProcesses) -decodeAllProcessesJson : Decoder AllProcessesJson -decodeAllProcessesJson = - Decode.succeed AllProcessesJson - |> JDP.required "textileProcesses" Decode.string - |> JDP.required "foodProcesses" Decode.string - - -processes : String -> (Result Http.Error AllProcessesJson -> msg) -> Cmd msg +processes : String -> (Result Http.Error RawJsonProcesses -> msg) -> Cmd msg processes token event = Http.request { method = "GET" , url = "processes/processes.json" , headers = [ Http.header "token" token ] , body = Http.emptyBody - , expect = Http.expectJson event decodeAllProcessesJson + , expect = Http.expectJson event Db.decodeRawJsonProcesses , timeout = Nothing , tracker = Nothing } diff --git a/src/Server/Request.elm b/src/Server/Request.elm index 8c2d3d185..64b735532 100644 --- a/src/Server/Request.elm +++ b/src/Server/Request.elm @@ -13,6 +13,6 @@ type alias Request = { method : String , url : String , body : Encode.Value - , processes : StaticJson.Processes + , processes : StaticJson.RawJsonProcesses , jsResponseHandler : Encode.Value } diff --git a/src/Static/Db.elm b/src/Static/Db.elm index 393aa9bc1..4f57fe593 100644 --- a/src/Static/Db.elm +++ b/src/Static/Db.elm @@ -1,7 +1,7 @@ module Static.Db exposing - ( AllProcessesJson - , Db + ( Db , db + , decodeRawJsonProcesses , processes , updateEcotoxWeighting , updateProcesses @@ -17,7 +17,9 @@ import Data.Textile.Db as TextileDb import Data.Textile.Process as TextileProcess import Data.Transport exposing (Distances) import Data.Unit as Unit -import Static.Json as StaticJson +import Json.Decode as Decode exposing (Decoder) +import Json.Decode.Pipeline as JDP +import Static.Json as StaticJson exposing (RawJsonProcesses) type alias Db = @@ -29,13 +31,7 @@ type alias Db = } -type alias AllProcessesJson = - { textileProcessesJson : String - , foodProcessesJson : String - } - - -db : StaticJson.Processes -> Result String Db +db : StaticJson.RawJsonProcesses -> Result String Db db procs = StaticJson.db procs |> Result.andThen @@ -50,12 +46,19 @@ db procs = ) +decodeRawJsonProcesses : Decoder RawJsonProcesses +decodeRawJsonProcesses = + Decode.succeed RawJsonProcesses + |> JDP.required "textileProcesses" Decode.string + |> JDP.required "foodProcesses" Decode.string + + impactDefinitions : Result String Definitions impactDefinitions = Common.impactsFromJson StaticJson.impactsJson -processes : StaticJson.Processes +processes : StaticJson.RawJsonProcesses processes = StaticJson.processes diff --git a/src/Static/Json.elm-template b/src/Static/Json.elm-template index e6f946f1b..04556f331 100644 --- a/src/Static/Json.elm-template +++ b/src/Static/Json.elm-template @@ -1,5 +1,5 @@ module Static.Json exposing - ( Processes + ( RawJsonProcesses , countriesJson , db , impactsJson @@ -7,16 +7,20 @@ module Static.Json exposing , transportsJson ) - import Data.Food.Db as FoodDb import Data.Textile.Db as TextileDb type alias Db = - { foodDb : FoodDb.Db, textileDb : TextileDb.Db } + { foodDb : FoodDb.Db + , textileDb : TextileDb.Db + } -type alias Processes = { foodProcesses : String, textileProcesses: String } +type alias RawJsonProcesses = + { foodProcesses : String + , textileProcesses : String + } impactsJson : String @@ -79,13 +83,13 @@ transportsJson = """%transportsJson%""" -processes : Processes +processes : RawJsonProcesses processes = { foodProcesses = foodProcessesJson , textileProcesses = textileProcessesJson } -db : Processes -> Result String Db +db : RawJsonProcesses -> Result String Db db { foodProcesses, textileProcesses } = Result.map2 Db (foodDb foodProcesses) (textileDb textileProcesses) From 3dad6c6f0bcc5d002660b64f48b1669e63f2de08 Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Tue, 9 Jul 2024 09:54:50 +0200 Subject: [PATCH 29/30] chore: remove encryption code We don't use it for now, it will be part of another PR --- .env.sample | 1 - .github/workflows/node.js.yml | 3 -- backend/backend/settings.py | 3 -- bin/decrypt | 14 ------ bin/encrypt | 14 ------ bin/generate-encrypt-key | 5 --- lib/crypto.js | 83 ----------------------------------- server.js | 1 - 8 files changed, 124 deletions(-) delete mode 100755 bin/decrypt delete mode 100755 bin/encrypt delete mode 100755 bin/generate-encrypt-key delete mode 100644 lib/crypto.js diff --git a/.env.sample b/.env.sample index f525238b0..6e9964b5e 100644 --- a/.env.sample +++ b/.env.sample @@ -3,7 +3,6 @@ BACKEND_ADMINS=foo@bar.baz,baz@bar.foo DEFAULT_FROM_EMAIL=bar@foo.baz DJANGO_DEBUG=True DJANGO_SECRET_KEY=please-change-this -ENCRYPTION_KEY=my_secret_encryptionkey_changeit EMAIL_HOST_PASSWORD=please-change-this EMAIL_HOST_USER=please-change-this ENABLE_FOOD_SECTION=True diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index e137c6db1..0abf3a22a 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -81,7 +81,4 @@ jobs: run: npm run test:client - name: Run server tests - env: - DJANGO_BYPASS_AUTH: True - ENCRYPTION_KEY: my_secret_encryptionkey_changeit run: pipenv run backend/update.sh && npm run test:server-ci && npm run test:backend diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 791c964e1..ae16cbb09 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -30,9 +30,6 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = config("DJANGO_DEBUG", cast=bool, default=False) -# SECURITY WARNING: keep the secret key used in production secret! -ENCRYPTION_KEY = config("ENCRYPTION_KEY", "dev_not_so_secret_key") - # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = config("DJANGO_SECRET_KEY", "dev_not_so_secret_key") diff --git a/bin/decrypt b/bin/decrypt deleted file mode 100755 index ef1dbc0e4..000000000 --- a/bin/decrypt +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env node - -const { decryptFile } = require("../lib/crypto"); - -const path = `${__dirname}/../${process.argv[2]}`; -console.log(`Decrypting ${path}…`); - -try { - decryptFile(path); - console.log("Done."); -} catch (err) { - console.error(`Couldn't decrypt ${path}:\n> ${err}\nWas it encoded?`); - process.exit(1); -} diff --git a/bin/encrypt b/bin/encrypt deleted file mode 100755 index 35dd4c432..000000000 --- a/bin/encrypt +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env node - -const { encryptFile } = require("../lib/crypto"); - -const path = `${__dirname}/../${process.argv[2]}`; -console.log(`Encrypting ${path}…`); - -try { - encryptFile(path); - console.log("Done."); -} catch (err) { - console.error(`Couldn't encrypt ${path}:\n> ${err}`); - process.exit(1); -} diff --git a/bin/generate-encrypt-key b/bin/generate-encrypt-key deleted file mode 100755 index 5c6121ba4..000000000 --- a/bin/generate-encrypt-key +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -const crypto = require("crypto"); -const key = crypto.createHash("sha512").digest("hex").substring(0, 32); -process.stdout.write(key + "\n"); diff --git a/lib/crypto.js b/lib/crypto.js deleted file mode 100644 index f75c0ac29..000000000 --- a/lib/crypto.js +++ /dev/null @@ -1,83 +0,0 @@ -require("dotenv").config(); -const fs = require("fs"); -const crypto = require("crypto"); - -// Secret key for encrypting and decrypting things -// If you need to generate a first secret key: crypto.createHash("sha512").digest("hex").substring(0, 32) -// Then store the result in an ENCRYPTION_KEY env var and never expose it publicly! -const { ENCRYPTION_KEY } = process.env; - -function encrypt(text, secret_key = ENCRYPTION_KEY) { - validateSecretKey(secret_key); - validateNotAlreadyEncrypted(text); - const iv = crypto.randomBytes(16); - const cipher = crypto.createCipheriv("aes-256-cbc", Buffer.from(secret_key), iv); - return { - iv: iv.toString("hex"), - encrypted: Buffer.concat([cipher.update(text), cipher.final()]).toString("hex"), - }; -} - -function decrypt({ encrypted, iv }, secret_key = ENCRYPTION_KEY) { - validateSecretKey(secret_key); - const decipher = crypto.createDecipheriv( - "aes-256-cbc", - Buffer.from(secret_key), - Buffer.from(iv, "hex"), - ); - return Buffer.concat([ - decipher.update(Buffer.from(encrypted, "hex")), - decipher.final(), - ]).toString(); -} - -function encryptFile(path, secret_key = ENCRYPTION_KEY) { - const contents = fs.readFileSync(path).toString("utf-8"); - const encryptedContents = encrypt(contents, secret_key); - fs.writeFileSync(path, JSON.stringify(encryptedContents)); -} - -function decryptFile(path, secret_key = ENCRYPTION_KEY) { - const encryptedContents = fs.readFileSync(path).toString("utf-8"); - fs.writeFileSync(path, decrypt(JSON.parse(encryptedContents), secret_key)); -} - -function validateNotAlreadyEncrypted(contents) { - try { - // Attempting at deciphering provided text should raise an expected Error - decrypt(JSON.parse(contents)); - } catch (err) { - return; - } - // If no error has been raised, we're likely to face already encypted text - throw new Error("Contents are already encrypted."); -} - -function validateSecretKey(key) { - if (String(key).length !== 32) { - throw new Error("Invalid secret key provided, which must be 32c length."); - } -} - -function demo() { - const textToEncrypt = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; - console.log("Texte à chiffrer:", textToEncrypt); - const encrypted = encrypt(textToEncrypt); - console.log("Données chiffrées:", encrypted); - console.log("Données déchiffrées:", decrypt(encrypted)); -} - -function demoFile() { - const path = `${__dirname}/../public/data/textile/processes_impacts.json`; - encryptFile(path); - decryptFile(path); -} - -module.exports = { - decrypt, - decryptFile, - demo, - demoFile, - encrypt, - encryptFile, -}; diff --git a/server.js b/server.js index f031dff9b..ba6988310 100644 --- a/server.js +++ b/server.js @@ -8,7 +8,6 @@ const helmet = require("helmet"); const Sentry = require("@sentry/node"); const { Elm } = require("./server-app"); const lib = require("./lib"); -const { encrypt } = require("./lib/crypto"); const app = express(); // web app const api = express(); // api app From b75a55469e3b5474aab474755e8867fb3f0d5180 Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Mon, 8 Jul 2024 15:19:59 +0200 Subject: [PATCH 30/30] chore: compute undetailed processes at server load --- compute-aggregated.js | 45 ------------- data/Makefile | 6 +- package.json | 1 - server.js | 29 +++++++-- src/ComputeAggregated.elm | 116 --------------------------------- src/Data/Impact.elm | 11 ---- src/Data/Impact/Definition.elm | 1 - 7 files changed, 25 insertions(+), 184 deletions(-) delete mode 100644 compute-aggregated.js delete mode 100644 src/ComputeAggregated.elm diff --git a/compute-aggregated.js b/compute-aggregated.js deleted file mode 100644 index f58589d5d..000000000 --- a/compute-aggregated.js +++ /dev/null @@ -1,45 +0,0 @@ -require("dotenv").config(); -const fs = require("fs"); -const { Elm } = require("./compute-aggregated-app"); - -const elmApp = Elm.ComputeAggregated.init({ - flags: { - definitionsString: fs.readFileSync("public/data/impacts.json", "utf-8"), - textileProcessesString: fs.readFileSync("public/data/textile/processes_impacts.json", "utf-8"), - foodProcessesString: fs.readFileSync("public/data/food/processes_impacts.json", "utf-8"), - }, -}); - -const exportJson = async (filepath, json) => { - // Using dynamic import to avoid jest runtime error - // eg. “A dynamic import callback was invoked without --experimental-vm-modules” - const prettier = require("prettier"); - - const jsonString = JSON.stringify(json, null, 2); - const formattedJson = await prettier.format(jsonString, { filepath }); - - fs.writeFileSync(filepath, formattedJson); -}; - -elmApp.ports.export.subscribe( - ({ - textileProcesses, - foodProcesses, - textileProcessesOnlyAggregated, - foodProcessesOnlyAggregated, - }) => { - try { - exportJson("public/data/textile/processes_impacts.json", textileProcesses); - exportJson("public/data/food/processes_impacts.json", foodProcesses); - exportJson("public/data/textile/processes.json", textileProcessesOnlyAggregated); - exportJson("public/data/food/processes.json", foodProcessesOnlyAggregated); - console.log("EXPORTED!"); - } catch (err) { - console.error(err); - } - }, -); - -elmApp.ports.logError.subscribe((errorMessage) => { - console.error("Error:", errorMessage); -}); diff --git a/data/Makefile b/data/Makefile index 9b7774045..dc52c6779 100644 --- a/data/Makefile +++ b/data/Makefile @@ -36,10 +36,8 @@ delete_textile_method: export_food: @if [ "$(shell docker container inspect -f '{{.State.Running}}' $(NAME) )" = "true" ]; then \ - $(DOCKEREXEC) -w /home/jovyan/ecobalyse/data/food $(NAME) python3 export.py && \ - $(DOCKEREXEC) -w /home/jovyan/ecobalyse/data/food $(NAME) npm run processes:build; \ - else $(DOCKERRUN) -w /home/jovyan/ecobalyse/data/food $(NAME) python3 export.py && \ - $(DOCKERRUN) -w /home/jovyan/ecobalyse/data/food $(NAME) npm run processes:build; fi + $(DOCKEREXEC) -w /home/jovyan/ecobalyse/data/food $(NAME) python3 export.py; \ + else $(DOCKERRUN) -w /home/jovyan/ecobalyse/data/food $(NAME) python3 export.py; fi compare_food: @if [ "$(shell docker container inspect -f '{{.State.Running}}' $(NAME) )" = "true" ]; then \ diff --git a/package.json b/package.json index 7791fdf6c..5a7972379 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "scripts": { "build": "npm run server:build && rimraf dist && npm run build:init && parcel build index.html --public-url ./", "build:init": "./bin/update-version.sh && mkdir -p dist && cp -r public/* dist/ && npm run db:build", - "processes:build": "elm make src/ComputeAggregated.elm --output=compute-aggregated-app.js && node compute-aggregated.js", "db:build": "./bin/build-db", "lint:prettier": "prettier --config .prettierrc --check", "lint:prettier:all": "npm run lint:prettier -- .", diff --git a/server.js b/server.js index ba6988310..10e998028 100644 --- a/server.js +++ b/server.js @@ -95,19 +95,36 @@ const apiTracker = lib.setupTracker(openApiContents); const textileImpactsFile = "public/data/textile/processes_impacts.json"; const foodImpactsFile = "public/data/food/processes_impacts.json"; -const textileFile = "public/data/textile/processes.json"; -const foodFile = "public/data/food/processes.json"; + +const foodProcessesString = fs.readFileSync(foodImpactsFile, "utf8"); +const textileProcessesString = fs.readFileSync(textileImpactsFile, "utf8"); const processesImpacts = { - foodProcesses: fs.readFileSync(foodImpactsFile, "utf8"), - textileProcesses: fs.readFileSync(textileImpactsFile, "utf8"), + foodProcesses: foodProcessesString, + textileProcesses: textileProcessesString, }; const processes = { - foodProcesses: fs.readFileSync(foodFile, "utf8"), - textileProcesses: fs.readFileSync(textileFile, "utf8"), + foodProcesses: removeProcessesImpacts(foodProcessesString), + textileProcesses: removeProcessesImpacts(textileProcessesString), }; +function removeProcessesImpacts(processesImpactsString) { + let processesImpactsJSON = JSON.parse(processesImpactsString); + + // Iterate through all the processes + for (let process of processesImpactsJSON) { + // Iterate through all impacts of the process + for (var impactKey of Object.keys(process["impacts"])) { + if (impactKey != "ecs" && impactKey != "pef") { + process["impacts"][impactKey] = 0; + } + } + } + + return JSON.stringify(processesImpactsJSON); +} + const getProcesses = async (token) => { let isTokenValid = false; if (token) { diff --git a/src/ComputeAggregated.elm b/src/ComputeAggregated.elm deleted file mode 100644 index 04b88dccd..000000000 --- a/src/ComputeAggregated.elm +++ /dev/null @@ -1,116 +0,0 @@ -port module ComputeAggregated exposing (main) - -import Data.Food.Process as FoodProcess -import Data.Impact as Impact exposing (Impacts) -import Data.Impact.Definition as Definition exposing (Definitions) -import Data.Textile.Process as TextileProcess -import Json.Decode as Decode exposing (Decoder) -import Json.Encode as Encode -import Quantity - - -type alias Flags = - { definitionsString : String - , textileProcessesString : String - , foodProcessesString : String - } - - -decodeProcesses : Decoder (List { a | impacts : Impacts }) -> Definitions -> String -> Result Decode.Error (List { a | impacts : Impacts }) -decodeProcesses decoder definitions processesString = - processesString - |> Decode.decodeString decoder - |> Result.map - (List.map - (\process -> - { process | impacts = Impact.updateAggregatedScores definitions process.impacts } - ) - ) - - -keepOnlyAggregated : List { a | impacts : Impacts } -> List { a | impacts : Impacts } -keepOnlyAggregated processes = - processes - |> List.map - (\process -> - { process - | impacts = - Impact.mapImpacts - (\def impact -> - if Definition.isAggregate def then - impact - - else - Quantity.zero - ) - process.impacts - } - ) - - -toExport : Flags -> Result Decode.Error Encode.Value -toExport { definitionsString, textileProcessesString, foodProcessesString } = - definitionsString - |> Decode.decodeString Definition.decode - |> Result.andThen - (\definitions -> - let - textileProcessesResult = - decodeProcesses (TextileProcess.decodeList Impact.decodeWithoutAggregated) definitions textileProcessesString - - foodProcessesResult = - decodeProcesses (FoodProcess.decodeList Impact.decodeWithoutAggregated) definitions foodProcessesString - in - Result.map2 - (\textileProcesses foodProcesses -> - let - textileProcessesOnlyAggregated = - textileProcesses - |> keepOnlyAggregated - - foodProcessesOnlyAggregated = - foodProcesses - |> keepOnlyAggregated - in - Encode.object - [ ( "textileProcesses", Encode.list TextileProcess.encode textileProcesses ) - , ( "foodProcesses", Encode.list FoodProcess.encode foodProcesses ) - , ( "textileProcessesOnlyAggregated", Encode.list TextileProcess.encode textileProcessesOnlyAggregated ) - , ( "foodProcessesOnlyAggregated", Encode.list FoodProcess.encode foodProcessesOnlyAggregated ) - ] - ) - textileProcessesResult - foodProcessesResult - ) - - -init : Flags -> ( (), Cmd () ) -init flags = - case toExport flags of - Ok encodedValue -> - ( () - , export encodedValue - ) - - Err error -> - ( () - , error - |> Decode.errorToString - |> Encode.string - |> logError - ) - - -main : Program Flags () () -main = - Platform.worker - { init = init - , update = \_ _ -> ( (), Cmd.none ) - , subscriptions = always Sub.none - } - - -port export : Encode.Value -> Cmd msg - - -port logError : Encode.Value -> Cmd msg diff --git a/src/Data/Impact.elm b/src/Data/Impact.elm index 2131b6b73..dc2bc2771 100644 --- a/src/Data/Impact.elm +++ b/src/Data/Impact.elm @@ -6,7 +6,6 @@ module Data.Impact exposing , applyComplements , complementsImpactAsChartEntries , decodeImpacts - , decodeWithoutAggregated , default , divideBy , divideComplementsImpactsBy @@ -44,7 +43,6 @@ import Data.Color as Color import Data.Impact.Definition as Definition exposing (Definition, Definitions, Trigram, Trigrams) import Data.Unit as Unit import Json.Decode as Decode exposing (Decoder) -import Json.Decode.Pipeline as Pipe import Json.Encode as Encode import Mass exposing (Mass) import Quantity @@ -424,15 +422,6 @@ decodeImpacts = |> Decode.map Impacts -decodeWithoutAggregated : Decoder Impacts -decodeWithoutAggregated = - Definition.decodeWithoutAggregated (always Unit.decodeImpact) - -- Those aggregated impacts will have to be computed after the decoding - |> Pipe.hardcoded Quantity.zero - |> Pipe.hardcoded Quantity.zero - |> Decode.map Impacts - - encode : Impacts -> Encode.Value encode (Impacts impacts) = impacts diff --git a/src/Data/Impact/Definition.elm b/src/Data/Impact/Definition.elm index 4c1db9417..213ae8ff2 100644 --- a/src/Data/Impact/Definition.elm +++ b/src/Data/Impact/Definition.elm @@ -6,7 +6,6 @@ module Data.Impact.Definition exposing , Trigrams , decode , decodeBase - , decodeWithoutAggregated , encodeBase , filter , foldl