Skip to content

Commit 7a1da3a

Browse files
committed
Secure session
1 parent 1b562f4 commit 7a1da3a

File tree

10 files changed

+122
-24
lines changed

10 files changed

+122
-24
lines changed

.gitignore

+7-1
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,10 @@ public/*
1818
!public/readme.txt
1919
session/assets/qr.svg
2020
.wwebjs_auth/*
21-
session.zip
21+
.wwebjs_auth
22+
session.zip
23+
session.txt
24+
session-output/*
25+
session-output
26+
session
27+
session.secure

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
<p align="center">
1111
<a href="https://github.com/tuhinpal/WhatsBot/releases">
12-
<img src="https://shields.io/badge/WHATSBOT-Version--1.6.0-red?logo=whatsapp&style=for-the-badge"
12+
<img src="https://shields.io/badge/WHATSBOT-Version--3.0.0-red?logo=whatsapp&style=for-the-badge"
1313
alt="Version"></a><br>
1414
<a href="https://github.com/tuhinpal/WhatsBot/wiki">
1515
<img src="https://shields.io/badge/WIKI-red?style=for-the-badge" alt="Wiki"></a>
@@ -64,7 +64,7 @@
6464
### Deploy :
6565

6666
[![Deploy with Heroku](https://www.herokucdn.com/deploy/button.svg "Deploy with Heroku")](https://heroku.com/deploy?template=https://github.com/tuhinpal/WhatsBot "Deploy with Heroku")<br>
67-
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2Ftuhinpal%2FWhatsBot&plugins=mongodb&envs=SESSION%2CPMPERMIT_ENABLED%2CDEFAULT_TR_LANG%2CENABLE_DELETE_ALERT%2COCR_SPACE_API_KEY&optionalEnvs=OCR_SPACE_API_KEY&SESSIONDesc=Puppeteer+Session.+Ge+it+by+running+genToken.js&PMPERMIT_ENABLEDDesc=Enable+Pmpermit+write+true+or+false+only&DEFAULT_TR_LANGDesc=Default+Translation+Language&ENABLE_DELETE_ALERTDesc=If+true+and+if+someone+delete+message+in+PM%2C+Bot+will+send+the+deleted+message+in+that+chat+%28Exclude+Media%29&OCR_SPACE_API_KEYDesc=Get+it+from+https%3A%2F%2Focr.space%2FOCRAPI&PMPERMIT_ENABLEDDefault=true&DEFAULT_TR_LANGDefault=en&referralCode=tuhin)
67+
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template/RTyLts?referralCode=tuhin)
6868

6969
### Commands :
7070

app.json

+8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66
"keywords": ["whatsbot", "whatsapp-bot", "whatsapp"],
77
"stack": "container",
88
"env": {
9+
"SESSION_KEY": {
10+
"description": "Session encryption password",
11+
"value": ""
12+
},
13+
"SESSION_URL": {
14+
"description": "session.secure public url (It is safe because it is encrypted, But still if you want maybe you can create some sort of authorization stuff)",
15+
"value": ""
16+
},
917
"PMPERMIT_ENABLED": {
1018
"description": "Enable Pmpermit write true or false only",
1119
"value": "true"

config.js

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const fs = require("fs");
33
require("dotenv").config();
44

55
module.exports = {
6+
session_key: process.env.SESSION_KEY,
67
pmpermit_enabled: process.env.PMPERMIT_ENABLED || "true",
78
mongodb_url: process.env.MONGODB_URL || process.env.MONGO_URL || "",
89
default_tr_lang: process.env.DEFAULT_TR_LANG || "en",

example.env

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
SESSION_KEY="tuhin"
12
PMPERMIT_ENABLED = 'true'
23
MONGODB_URL = ''
34
DEFAULT_TR_LANG = 'en'

examples/example.env.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ The `.env` file is used to initialize all enviroment variables in the developmen
55
Create the `.env` file in the root of your app and add your variables and values to it. These are all the variables that are needed by WhatsBot. Description about these can be found in [app.json](./app.json).
66

77
```env
8-
SESSION = ""
8+
SESSION_KEY=""
9+
SESSION_URL=""
910
PMPERMIT_ENABLED = ""
1011
MONGODB_URL = ""
1112
YT_DATA_API_KEY = ""
@@ -19,13 +20,15 @@ It is not mandatory to add all the variables in the `.env` file. Most of these h
1920
At the bare minimum, you need to initialize atleast the following variables to make it work in your local environment.
2021

2122
```env
22-
SESSION = ""
23+
SESSION_KEY = ""
2324
MONGODB_URL = ""
2425
YT_DATA_API_KEY = ""
2526
OCR_SPACE_API_KEY = ""
2627
```
2728

28-
- SESSION : Puppeteer Session. Get it by running genToken.js. As getToken.js creates a session.json file, this might not be necessary in a local environment as this variable has a fallback to the session.json file. It is mentioned here as it is the most inportant information needed by the bot to work.
29+
- SESSION_KEY : Your `session.secure` encryption key.
30+
31+
- SESSION_URL : You can retrieve your session from an public hosted endpoint, This is just a file and this is secured with `SESSION_KEY`. Don't put it if you are using VPS or your Local machine.
2932

3033
- YT_DATA_API_KEY : Youtube DATA API key, grab it from <https:/>/cloud.google.com>.
3134

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "whatsbot",
3-
"version": "2.1.1",
3+
"version": "3.0.0",
44
"description": "Plugable U***b*t for WhatsApp",
55
"main": "main.js",
66
"scripts": {
@@ -23,6 +23,7 @@
2323
"dependencies": {
2424
"@iamtraction/google-translate": "^1.1.2",
2525
"adm-zip": "^0.5.9",
26+
"aes-encrypt-stream": "^0.2.0",
2627
"axios": "^0.21.0",
2728
"child_process": "^1.0.2",
2829
"dotenv": "^10.0.0",

session/genToken.js

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,32 @@
11
const { Client, LocalAuth } = require("whatsapp-web.js");
22
const qrcode = require("qrcode-terminal");
33
const { write, clean } = require("./manage");
4+
const readline = require("readline");
45

56
clean();
7+
68
const client = new Client({
79
puppeteer: { headless: true, args: ["--no-sandbox"] },
810
authStrategy: new LocalAuth({ clientId: "whatsbot" }),
911
});
10-
client.initialize();
12+
13+
let password = null;
14+
15+
const rl = readline.createInterface({
16+
input: process.stdin,
17+
output: process.stdout,
18+
});
19+
20+
rl.question(
21+
"Enter password to encrypt session (You need to put this in ENV): ",
22+
(answer) => {
23+
password = answer;
24+
console.log("Password set to:", password);
25+
console.log("Generating QR Code...");
26+
rl.close();
27+
client.initialize();
28+
}
29+
);
1130

1231
client.on("qr", (qr) => {
1332
console.log(`Scan this QR Code and copy the JSON\n`);
@@ -20,7 +39,7 @@ client.on("ready", () => {
2039
// wait because filesystem is busy
2140
setTimeout(async () => {
2241
console.log("Session has been created");
23-
await write();
42+
await write(password);
2443
process.exit();
2544
}, 3000);
2645
});

session/manage.js

+61-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
const fs = require("fs");
22
const AdmZip = require("adm-zip");
3+
const {
4+
createEncryptStream,
5+
setPassword,
6+
createDecryptStream,
7+
} = require("aes-encrypt-stream");
8+
const crypto = require("crypto");
9+
const config = require("../config");
10+
const axios = require("axios");
11+
require("dotenv").config();
312

413
let base = `${__dirname}/../.wwebjs_auth/`;
514

@@ -21,26 +30,72 @@ module.exports = {
2130
}
2231
} catch (_) {}
2332
},
24-
write: async function write() {
33+
write: async function write(password) {
2534
excludedDir.forEach((dir) => {
2635
try {
2736
fs.rmSync(`${base}${dir}/`, { recursive: true });
2837
} catch (_) {}
2938
});
3039
const zip = new AdmZip();
3140
zip.addLocalFolder(base);
32-
await zip.writeZipPromise(`${__dirname}/../session.zip`);
41+
await zip.writeZipPromise(`${__dirname}/temp.zip`);
42+
setPassword(getCipherKey(password));
43+
await new Promise((resolve) => {
44+
createEncryptStream(fs.createReadStream(`${__dirname}/temp.zip`))
45+
.pipe(fs.createWriteStream(`${__dirname}/../session.secure`))
46+
.on("finish", () => {
47+
resolve();
48+
});
49+
});
50+
fs.unlinkSync(`${__dirname}/temp.zip`);
3351
},
34-
replicate: function replicate() {
52+
replicate: async function replicate() {
3553
try {
36-
let zipped = fs.readFileSync(`${__dirname}/../session.zip`);
37-
let unzip = new AdmZip(zipped);
54+
setPassword(getCipherKey(config.session_key));
55+
await new Promise((resolve) => {
56+
fs.createReadStream(`${__dirname}/../session.secure`)
57+
.pipe(
58+
createDecryptStream(fs.createWriteStream(`${__dirname}/temp.zip`))
59+
)
60+
.on("finish", () => {
61+
resolve();
62+
});
63+
});
64+
65+
let unzip = new AdmZip(fs.readFileSync(`${__dirname}/temp.zip`));
3866
unzip.extractAllToAsync(base, true);
3967
console.log("Session files replicated");
4068
} catch (error) {
4169
throw new Error(
42-
`Session file not found or corrupted. ${error.toString()}`
70+
`Session file not found, corrupted or password not matched. ${error.toString()}`
71+
);
72+
} finally {
73+
try {
74+
fs.unlinkSync(`${__dirname}/temp.zip`);
75+
} catch (_) {}
76+
}
77+
},
78+
fetchSession: async function fetchSession() {
79+
try {
80+
if (process.env.SESSION_URL) {
81+
let response = await axios.get(process.env.SESSION_URL, {
82+
responseType: "arraybuffer",
83+
});
84+
fs.writeFileSync(`${__dirname}/../session.secure`, response.data, {
85+
encoding: "binary",
86+
});
87+
console.log("Session file fetched from", process.env.SESSION_URL);
88+
} else {
89+
console.log("Using local session");
90+
}
91+
} catch (error) {
92+
throw new Error(
93+
`Session fetching failed. If you are using Local machine or VPS please remove 'SESSION_URL' from Enviroment Variable. ${error.toString()}`
4394
);
4495
}
4596
},
4697
};
98+
99+
function getCipherKey(password) {
100+
return crypto.createHash("sha256").update(password).digest();
101+
}

startProcess.js

+13-9
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
const { replicate, clean } = require("./session/manage");
1+
const { replicate, clean, fetchSession } = require("./session/manage");
22

3-
try {
4-
clean();
5-
replicate();
6-
setTimeout(() => {
7-
require("./main");
8-
}, 2000);
9-
} catch (error) {
10-
console.error(error?.message);
3+
async function main() {
4+
try {
5+
clean();
6+
await fetchSession();
7+
await replicate();
8+
setTimeout(() => {
9+
require("./main");
10+
}, 2000);
11+
} catch (error) {
12+
console.error(error?.message);
13+
}
1114
}
15+
main();

0 commit comments

Comments
 (0)