angelos 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +186 -2
- package/dist/index.cjs +14 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -6
- package/dist/index.d.ts +10 -6
- package/dist/index.mjs +14 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -1,3 +1,187 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @wa-auth/verifier
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Zero-cost, inbound WhatsApp OTP authentication for Node.js.
|
|
4
|
+
|
|
5
|
+
Instead of paying for outbound SMS gateways or paid Meta template conversations, verify users by letting them send a pre-filled WhatsApp message back to your Meta Business number. The package handles QR code generation, deep-links, Meta Cloud API webhook signature verification, and replay-protected token comparison.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Architecture Overview
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
User Browser Your Server Meta Cloud API
|
|
13
|
+
| | |
|
|
14
|
+
|--- 1. POST /auth/init ---------->| |
|
|
15
|
+
| (phone: "919876543210") | |
|
|
16
|
+
| |--- 2. createSession() --------|
|
|
17
|
+
|<-- 3. Returns QR & Deep Link ----| (stores OTP in memory/DB) |
|
|
18
|
+
| |
|
|
19
|
+
|==== 4. User sends "VERIFY <UUID> <OTP>" via WhatsApp ===========>|
|
|
20
|
+
| |
|
|
21
|
+
| |<-- 5. POST /webhook ----------|
|
|
22
|
+
| | (signed with App Secret) |
|
|
23
|
+
| | |
|
|
24
|
+
| |--- 6. verifyWebhook() --------|
|
|
25
|
+
| | - checks signature |
|
|
26
|
+
| | - compares sender phone |
|
|
27
|
+
| | - drops token from store |
|
|
28
|
+
|<-- 7. Poll or SSE detects match -| |
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @wa-auth/verifier
|
|
35
|
+
```
|
|
36
|
+
Requires Node.js 18.0.0 or higher.
|
|
37
|
+
|
|
38
|
+
## Meta Dashboard Setup
|
|
39
|
+
|
|
40
|
+
1. Go to [developers.facebook.com](https://developers.facebook.com/) and create or open your Business App.
|
|
41
|
+
2. Under **WhatsApp > Configuration**:
|
|
42
|
+
- **Callback URL:** `https://yourdomain.com/api/webhook/whatsapp` (use ngrok for local testing).
|
|
43
|
+
- **Verify Token:** Any custom string you choose (e.g. `MY_SECRET_HANDSHAKE_TOKEN`).
|
|
44
|
+
- Subscribe to the `messages` webhook field.
|
|
45
|
+
3. Under **App settings > Basic**:
|
|
46
|
+
- Copy your **App Secret** to verify incoming request signatures.
|
|
47
|
+
4. Copy your registered **WhatsApp Business Phone Number** (in E.164 format without `+`).
|
|
48
|
+
|
|
49
|
+
## Usage Example (Express.js)
|
|
50
|
+
|
|
51
|
+
### 1. Initialize the Verifier
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// src/verifier.ts
|
|
55
|
+
import { WhatsAppOtpVerifier } from "@wa-auth/verifier";
|
|
56
|
+
|
|
57
|
+
export const verifier = new WhatsAppOtpVerifier({
|
|
58
|
+
businessPhoneNumber: process.env.WHATSAPP_PHONE_NUMBER!,
|
|
59
|
+
webhookVerifyToken: process.env.META_VERIFY_TOKEN!,
|
|
60
|
+
appSecret: process.env.META_APP_SECRET, // Validates X-Hub-Signature-256
|
|
61
|
+
defaultTtlSeconds: 180, // 3‑minute validity
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 2. Configure Raw Body Parsing (Required for Signature Verification)
|
|
66
|
+
Meta signs every incoming POST webhook using HMAC‑SHA256 based on the raw payload bytes. Capture the unparsed buffer in Express:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
// src/server.ts
|
|
70
|
+
import express from "express";
|
|
71
|
+
import authRoutes from "./routes/auth.routes.js";
|
|
72
|
+
|
|
73
|
+
const app = express();
|
|
74
|
+
|
|
75
|
+
app.use(
|
|
76
|
+
express.json({
|
|
77
|
+
verify: (req: any, _res, buf) => {
|
|
78
|
+
req.rawBody = buf;
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
app.use("/api", authRoutes);
|
|
84
|
+
|
|
85
|
+
app.listen(3000, () => {
|
|
86
|
+
console.log("Server listening on port 3000");
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3. Setup Routes and Controllers
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// src/routes/auth.routes.ts
|
|
94
|
+
import { Router, Request, Response } from "express";
|
|
95
|
+
import { verifier } from "../verifier.js";
|
|
96
|
+
|
|
97
|
+
const router = Router();
|
|
98
|
+
|
|
99
|
+
// In‑memory lookup for verified states (use DB or Redis in production)
|
|
100
|
+
const verifiedSessions = new Set<string>();
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 1. Client requests authentication targets
|
|
104
|
+
*/
|
|
105
|
+
router.post("/auth/init", async (req: Request, res: Response) => {
|
|
106
|
+
try {
|
|
107
|
+
const { phone } = req.body;
|
|
108
|
+
if (!phone) {
|
|
109
|
+
return res.status(400).json({ error: "Missing phone number" });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const session = await verifier.createSession({
|
|
113
|
+
phoneNumber: phone,
|
|
114
|
+
ttl: 180,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return res.status(200).json({
|
|
118
|
+
sessionId: session.id,
|
|
119
|
+
qrCode: session.qrLink, // Data URL for <img> tags
|
|
120
|
+
deepLink: session.deepLink, // wa.me link
|
|
121
|
+
appLink: session.qrLink, // whatsapp:// link (same as qrLink here)
|
|
122
|
+
expiresAt: session.expiresAt,
|
|
123
|
+
});
|
|
124
|
+
} catch (error: any) {
|
|
125
|
+
return res.status(500).json({ error: error.message });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 2. Meta Webhook Handshake (GET)
|
|
131
|
+
*/
|
|
132
|
+
router.get("/webhook/whatsapp", (req: Request, res: Response) => {
|
|
133
|
+
const challenge = verifier.handleWebhookVerification(req.query);
|
|
134
|
+
if (challenge) {
|
|
135
|
+
return res.status(200).send(challenge);
|
|
136
|
+
}
|
|
137
|
+
return res.sendStatus(403);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 3. Meta Webhook Ingestion (POST)
|
|
142
|
+
*/
|
|
143
|
+
router.post("/webhook/whatsapp", async (req: Request, res: Response) => {
|
|
144
|
+
// Acknowledge Meta immediately with 200 OK
|
|
145
|
+
res.status(200).send("EVENT_RECEIVED");
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const signature = req.headers["x-hub-signature-256"] as string | undefined;
|
|
149
|
+
const rawBuffer = (req as any).rawBody;
|
|
150
|
+
|
|
151
|
+
const result = await verifier.verifySession({
|
|
152
|
+
rawBody: rawBuffer,
|
|
153
|
+
signature,
|
|
154
|
+
jsonData: req.body,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (result.success && result.parsedData?.sendersPhoneNumber) {
|
|
158
|
+
verifiedSessions.add(result.parsedData?.sendersPhoneNumber);
|
|
159
|
+
console.log(`Phone +${result.parsedData.sendersPhoneNumber} verified.`);
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error("Webhook processing error:", error);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 4. Client polls this endpoint to verify completion
|
|
168
|
+
*/
|
|
169
|
+
router.get("/auth/status", (req: Request, res: Response) => {
|
|
170
|
+
const sessionId = req.query.sessionId as string;
|
|
171
|
+
if (!sessionId) {
|
|
172
|
+
return res.status(400).json({ error: "Missing sessionId" });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const isVerified = verifiedSessions.has(sessionId);
|
|
176
|
+
return res.status(200).json({
|
|
177
|
+
status: isVerified ? "VERIFIED" : "PENDING",
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
export default router;
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## API Reference
|
|
185
|
+
|
|
186
|
+
### `new WhatsAppOtpVerifier(config)`
|
|
187
|
+
... _(rest of README continues as originally provided)_
|
package/dist/index.cjs
CHANGED
|
@@ -69,16 +69,21 @@ function generateKey(phoneNumber) {
|
|
|
69
69
|
// src/whatsappverifier.ts
|
|
70
70
|
var MIN_TTL_SECONDS = 30;
|
|
71
71
|
var MAX_TTL_SECONDS = 3600;
|
|
72
|
-
var
|
|
72
|
+
var WhatsAppOtpVerifier = class {
|
|
73
73
|
storage;
|
|
74
74
|
businessPhoneNumber;
|
|
75
|
-
|
|
75
|
+
webhookVerifyToken;
|
|
76
|
+
appSecret;
|
|
76
77
|
defaultTtl;
|
|
77
78
|
constructor(config) {
|
|
78
79
|
this.storage = config.storage;
|
|
79
80
|
this.businessPhoneNumber = config.businessPhoneNumber;
|
|
80
|
-
this.
|
|
81
|
-
this.
|
|
81
|
+
this.webhookVerifyToken = config.webhookVerifyToken;
|
|
82
|
+
this.appSecret = config.appSecret;
|
|
83
|
+
this.defaultTtl = this.validateTtl(
|
|
84
|
+
config.defaultTtl ?? config.defaultTtlSeconds ?? 180,
|
|
85
|
+
"defaultTtl"
|
|
86
|
+
);
|
|
82
87
|
}
|
|
83
88
|
validateTtl(ttl, label) {
|
|
84
89
|
if (!Number.isFinite(ttl) || ttl < MIN_TTL_SECONDS || ttl > MAX_TTL_SECONDS) {
|
|
@@ -119,14 +124,15 @@ var whatsappVerifier = class {
|
|
|
119
124
|
dataLink,
|
|
120
125
|
deepLink,
|
|
121
126
|
qrLink,
|
|
122
|
-
qrSvg
|
|
127
|
+
qrSvg,
|
|
128
|
+
expiresAt
|
|
123
129
|
};
|
|
124
130
|
}
|
|
125
131
|
async verifySession(params) {
|
|
126
132
|
const rawBody = params.rawBody;
|
|
127
133
|
const jsonData = params.jsonData;
|
|
128
134
|
const signature = params.signature;
|
|
129
|
-
const isSignatureValid = verifyMetaSignature(rawBody, signature, this.
|
|
135
|
+
const isSignatureValid = verifyMetaSignature(rawBody, signature, this.appSecret ?? "");
|
|
130
136
|
if (!isSignatureValid) {
|
|
131
137
|
return {
|
|
132
138
|
success: false,
|
|
@@ -185,13 +191,13 @@ var whatsappVerifier = class {
|
|
|
185
191
|
const mode = query["hub.mode"];
|
|
186
192
|
const token = query["hub.verify_token"];
|
|
187
193
|
const challenge = query["hub.challenge"];
|
|
188
|
-
if (mode === "subscribe" && token === this.
|
|
194
|
+
if (mode === "subscribe" && token === this.webhookVerifyToken) {
|
|
189
195
|
return { success: true, challenge };
|
|
190
196
|
}
|
|
191
197
|
return { success: false };
|
|
192
198
|
}
|
|
193
199
|
};
|
|
194
|
-
var whatsappverifier_default =
|
|
200
|
+
var whatsappverifier_default = WhatsAppOtpVerifier;
|
|
195
201
|
|
|
196
202
|
// src/index.ts
|
|
197
203
|
var index_default = whatsappverifier_default;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/qr/qrGenerator.ts","../src/webhooks/signature.ts","../src/webhooks/parser.ts","../src/keyGenerator/keyGenerator.ts","../src/whatsappverifier.ts","../src/index.ts"],"names":["QRCode","crypto"],"mappings":"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","file":"index.cjs","sourcesContent":["import QRCode from \"qrcode\";\nimport { GeneratedOrCode } from \"../types/index.js\";\n\nexport const generateQrFromData = async (\n phoneNumber: string,\n token: string,\n): Promise<GeneratedOrCode> => {\n const encodedText = encodeURIComponent(token);\n const dataLink = `https://wa.me/${phoneNumber}?text=${encodedText}`;\n const deepLink = `whatsapp://send?phone=${phoneNumber}&text=${encodedText}`;\n\n const qrLink = await QRCode.toDataURL(dataLink, {\n width: 300,\n margin: 2,\n errorCorrectionLevel: \"M\",\n });\n\n const qrSvg = await QRCode.toString(dataLink, {\n type: \"svg\",\n margin: 2,\n errorCorrectionLevel: \"M\",\n });\n\n return {\n dataLink: dataLink,\n deepLink: deepLink,\n qrLink: qrLink,\n qrSvg: qrSvg,\n } as GeneratedOrCode;\n};\n","import crypto from \"node:crypto\";\n\nexport const verifyMetaSignature = (\n rawPayload: Buffer,\n signatureHeader: string,\n secretKey: string,\n) => {\n if (!rawPayload || !signatureHeader || !secretKey) return false;\n\n const [algorithm, signature] = signatureHeader.split(\"=\");\n\n const expectedSignature = crypto\n .createHmac(\"sha256\", secretKey)\n .update(rawPayload)\n .digest(\"hex\");\n\n const signatureBuffer = Buffer.from(signature, \"utf8\");\n const expectedBuffer = Buffer.from(expectedSignature, \"utf8\");\n if (signatureBuffer.length !== expectedBuffer.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(signatureBuffer, expectedBuffer);\n};\n","import { ParsedData } from \"../types/index.js\";\nimport { MetaWebhookPayload } from \"../types/internal.js\";\n\nexport const parser = (jsonData: MetaWebhookPayload): ParsedData => {\n try {\n const entry = jsonData?.entry?.[0];\n const change = entry?.changes?.[0];\n const value = change?.value;\n const message = value?.messages?.[0];\n\n if (!message || message.type !== \"text\") return null;\n\n return {\n sendersPhoneNumber: message.from,\n text: message.text.body,\n messageId: message.id,\n timestamp: message.timestamp,\n };\n } catch (error) {\n return null;\n }\n};\n","export function generateKey(phoneNumber: string): string {\n return `wa_verify:${phoneNumber}`;\n}\n","import { generateQrFromData } from \"./qr/qrGenerator.js\";\nimport {\n Config,\n CreateSessionConfig,\n CreateSessionResult,\n StorageAdapter,\n StorageData,\n VerifySession,\n VerifyWebhooks,\n WebhookVerificationQuery,\n WebhookVerificationResult,\n} from \"./types/index.js\";\nimport crypto from \"node:crypto\";\nimport { verifyMetaSignature } from \"./webhooks/signature.js\";\nimport { parser } from \"./webhooks/parser.js\";\nimport { generateKey } from \"./keyGenerator/keyGenerator.js\";\n\nconst MIN_TTL_SECONDS = 30;\nconst MAX_TTL_SECONDS = 3600;\n\nclass whatsappVerifier {\n private storage: StorageAdapter;\n private businessPhoneNumber: string;\n private waVerificationKey: string;\n private defaultTtl: number;\n\n constructor(config: Config) {\n this.storage = config.storage;\n this.businessPhoneNumber = config.businessPhoneNumber;\n this.waVerificationKey = config.waVerificationKey;\n this.defaultTtl = this.validateTtl(config.defaultTtl, \"defaultTtl\");\n }\n\n private validateTtl(ttl: number, label: string): number {\n if (!Number.isFinite(ttl) || ttl < MIN_TTL_SECONDS || ttl > MAX_TTL_SECONDS) {\n throw new RangeError(\n `${label} must be between ${MIN_TTL_SECONDS} and ${MAX_TTL_SECONDS} seconds, got ${ttl}`,\n );\n }\n return ttl;\n }\n\n public async createSession(params: CreateSessionConfig): Promise<CreateSessionResult> {\n const id = crypto.randomUUID();\n const cleanPhone = params.phoneNumber.replace(/\\D/g, \"\");\n const ttl = params.ttl ? this.validateTtl(params.ttl, \"ttl\") : this.defaultTtl;\n const token = crypto.randomInt(100000, 999999).toString();\n const expiresAt = ttl * 1000 + Date.now();\n\n const sessionData: StorageData = {\n sessionId: id,\n expectedPhoneNumber: cleanPhone,\n token: token,\n expiresAt: expiresAt,\n };\n\n const key = generateKey(cleanPhone);\n\n try {\n await this.storage.set(key, JSON.stringify(sessionData), expiresAt);\n } catch (error: unknown) {\n throw new Error(\n `Failed to store session data: ${error instanceof Error ? error.message : error}`,\n );\n }\n\n const message = `VERIFY ${id} ${token} ${expiresAt}`;\n const { dataLink, deepLink, qrLink, qrSvg } = await generateQrFromData(\n this.businessPhoneNumber,\n message,\n );\n\n return {\n id,\n token,\n dataLink,\n deepLink,\n qrLink,\n qrSvg,\n };\n }\n\n public async verifySession(params: VerifyWebhooks): Promise<VerifySession> {\n const rawBody = params.rawBody;\n const jsonData = params.jsonData;\n const signature = params.signature;\n\n const isSignatureValid = verifyMetaSignature(rawBody, signature, this.waVerificationKey);\n if (!isSignatureValid) {\n return {\n success: false,\n message: \"Invalid webhook signature\",\n };\n }\n\n const parsedData = parser(jsonData);\n if (!parsedData) {\n return {\n success: false,\n message: \"Invalid or unsupported webhook message\",\n };\n }\n\n const key = generateKey(parsedData.sendersPhoneNumber.replace(/\\D/g, \"\"));\n let userData;\n\n try {\n const data = await this.storage.get(key);\n if (!data) {\n return {\n success: false,\n message: \"Session data not found\",\n };\n }\n\n userData = JSON.parse(data) as StorageData;\n await this.storage.delete(key);\n } catch (error: unknown) {\n return {\n success: false,\n message: `Failed to get/delete session data: ${error instanceof Error ? error.message : error}`,\n };\n }\n\n const messageKeyword = parsedData.text.trim().split(\" \");\n const id = messageKeyword[1];\n const token = messageKeyword[2];\n const expiresAt = messageKeyword[3];\n\n const tokenBuffer = Buffer.from(token ?? \"\", \"utf8\");\n const expectedTokenBuffer = Buffer.from(userData.token, \"utf8\");\n\n const senderPhone = parsedData.sendersPhoneNumber.replace(/\\D/g, \"\");\n const phoneBuffer = Buffer.from(senderPhone, \"utf8\");\n const expectedPhoneBuffer = Buffer.from(userData.expectedPhoneNumber, \"utf8\");\n\n const isTokenValid =\n tokenBuffer.length === expectedTokenBuffer.length &&\n crypto.timingSafeEqual(tokenBuffer, expectedTokenBuffer);\n\n const isPhoneValid =\n phoneBuffer.length === expectedPhoneBuffer.length &&\n crypto.timingSafeEqual(phoneBuffer, expectedPhoneBuffer);\n\n if (!isTokenValid || !isPhoneValid || Number(expiresAt) < Date.now()) {\n return {\n success: false,\n message: \"Token is invalid or expired\",\n };\n }\n\n return {\n success: true,\n message: \"Successfully parsed the data\",\n parsedData: parsedData,\n };\n }\n\n public handleWebhookVerification(query: WebhookVerificationQuery): WebhookVerificationResult {\n const mode = query[\"hub.mode\"];\n const token = query[\"hub.verify_token\"];\n const challenge = query[\"hub.challenge\"];\n\n if (mode === \"subscribe\" && token === this.waVerificationKey) {\n return { success: true, challenge };\n }\n\n return { success: false };\n }\n}\n\nexport default whatsappVerifier;\n","import whatsappVerifier from \"./whatsappverifier.js\";\n\nexport default whatsappVerifier;\nexport * from \"./types/index.js\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/qr/qrGenerator.ts","../src/webhooks/signature.ts","../src/webhooks/parser.ts","../src/keyGenerator/keyGenerator.ts","../src/whatsappverifier.ts","../src/index.ts"],"names":["QRCode","crypto"],"mappings":"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","file":"index.cjs","sourcesContent":["import QRCode from \"qrcode\";\nimport { GeneratedOrCode } from \"../types/index.js\";\n\nexport const generateQrFromData = async (\n phoneNumber: string,\n token: string,\n): Promise<GeneratedOrCode> => {\n const encodedText = encodeURIComponent(token);\n const dataLink = `https://wa.me/${phoneNumber}?text=${encodedText}`;\n const deepLink = `whatsapp://send?phone=${phoneNumber}&text=${encodedText}`;\n\n const qrLink = await QRCode.toDataURL(dataLink, {\n width: 300,\n margin: 2,\n errorCorrectionLevel: \"M\",\n });\n\n const qrSvg = await QRCode.toString(dataLink, {\n type: \"svg\",\n margin: 2,\n errorCorrectionLevel: \"M\",\n });\n\n return {\n dataLink: dataLink,\n deepLink: deepLink,\n qrLink: qrLink,\n qrSvg: qrSvg,\n } as GeneratedOrCode;\n};\n","import crypto from \"node:crypto\";\n\nexport const verifyMetaSignature = (\n rawPayload: Buffer,\n signatureHeader: string,\n secretKey: string,\n) => {\n if (!rawPayload || !signatureHeader || !secretKey) return false;\n\n const [algorithm, signature] = signatureHeader.split(\"=\");\n\n const expectedSignature = crypto\n .createHmac(\"sha256\", secretKey)\n .update(rawPayload)\n .digest(\"hex\");\n\n const signatureBuffer = Buffer.from(signature, \"utf8\");\n const expectedBuffer = Buffer.from(expectedSignature, \"utf8\");\n if (signatureBuffer.length !== expectedBuffer.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(signatureBuffer, expectedBuffer);\n};\n","import { ParsedData } from \"../types/index.js\";\nimport { MetaWebhookPayload } from \"../types/internal.js\";\n\nexport const parser = (jsonData: MetaWebhookPayload): ParsedData => {\n try {\n const entry = jsonData?.entry?.[0];\n const change = entry?.changes?.[0];\n const value = change?.value;\n const message = value?.messages?.[0];\n\n if (!message || message.type !== \"text\") return null;\n\n return {\n sendersPhoneNumber: message.from,\n text: message.text.body,\n messageId: message.id,\n timestamp: message.timestamp,\n };\n } catch (error) {\n return null;\n }\n};\n","export function generateKey(phoneNumber: string): string {\n return `wa_verify:${phoneNumber}`;\n}\n","import { generateQrFromData } from \"./qr/qrGenerator.js\";\nimport {\n Config,\n CreateSessionConfig,\n CreateSessionResult,\n StorageAdapter,\n StorageData,\n VerifySession,\n VerifyWebhooks,\n WebhookVerificationQuery,\n WebhookVerificationResult,\n} from \"./types/index.js\";\nimport crypto from \"node:crypto\";\nimport { verifyMetaSignature } from \"./webhooks/signature.js\";\nimport { parser } from \"./webhooks/parser.js\";\nimport { generateKey } from \"./keyGenerator/keyGenerator.js\";\n\nconst MIN_TTL_SECONDS = 30;\nconst MAX_TTL_SECONDS = 3600;\n\nclass WhatsAppOtpVerifier {\n private storage: StorageAdapter;\n private businessPhoneNumber: string;\n private webhookVerifyToken: string;\n private appSecret?: string; \n private defaultTtl: number;\n\n constructor(config: Config) {\n this.storage = config.storage;\n this.businessPhoneNumber = config.businessPhoneNumber;\n this.webhookVerifyToken = config.webhookVerifyToken;\n this.appSecret = config.appSecret;\n this.defaultTtl = this.validateTtl(\n config.defaultTtl ?? config.defaultTtlSeconds ?? 180,\n \"defaultTtl\",\n );\n }\n\n private validateTtl(ttl: number, label: string): number {\n if (!Number.isFinite(ttl) || ttl < MIN_TTL_SECONDS || ttl > MAX_TTL_SECONDS) {\n throw new RangeError(\n `${label} must be between ${MIN_TTL_SECONDS} and ${MAX_TTL_SECONDS} seconds, got ${ttl}`,\n );\n }\n return ttl;\n }\n\n public async createSession(params: CreateSessionConfig): Promise<CreateSessionResult> {\n const id = crypto.randomUUID();\n const cleanPhone = params.phoneNumber.replace(/\\D/g, \"\");\n const ttl = params.ttl ? this.validateTtl(params.ttl, \"ttl\") : this.defaultTtl;\n const token = crypto.randomInt(100000, 999999).toString();\n const expiresAt = ttl * 1000 + Date.now();\n\n const sessionData: StorageData = {\n sessionId: id,\n expectedPhoneNumber: cleanPhone,\n token: token,\n expiresAt: expiresAt,\n };\n\n const key = generateKey(cleanPhone);\n\n try {\n await this.storage.set(key, JSON.stringify(sessionData), expiresAt);\n } catch (error: unknown) {\n throw new Error(\n `Failed to store session data: ${error instanceof Error ? error.message : error}`,\n );\n }\n\n const message = `VERIFY ${id} ${token} ${expiresAt}`;\n const { dataLink, deepLink, qrLink, qrSvg } = await generateQrFromData(\n this.businessPhoneNumber,\n message,\n );\n\n return {\n id,\n token,\n dataLink,\n deepLink,\n qrLink,\n qrSvg,\n expiresAt,\n };\n }\n\n public async verifySession(params: VerifyWebhooks): Promise<VerifySession> {\n const rawBody = params.rawBody;\n const jsonData = params.jsonData;\n const signature = params.signature;\n\n const isSignatureValid = verifyMetaSignature(rawBody, signature, this.appSecret ?? \"\");\n if (!isSignatureValid) {\n return {\n success: false,\n message: \"Invalid webhook signature\",\n };\n }\n\n const parsedData = parser(jsonData);\n if (!parsedData) {\n return {\n success: false,\n message: \"Invalid or unsupported webhook message\",\n };\n }\n\n const key = generateKey(parsedData.sendersPhoneNumber.replace(/\\D/g, \"\"));\n let userData;\n\n try {\n const data = await this.storage.get(key);\n if (!data) {\n return {\n success: false,\n message: \"Session data not found\",\n };\n }\n\n userData = JSON.parse(data) as StorageData;\n await this.storage.delete(key);\n } catch (error: unknown) {\n return {\n success: false,\n message: `Failed to get/delete session data: ${error instanceof Error ? error.message : error}`,\n };\n }\n\n const messageKeyword = parsedData.text.trim().split(\" \");\n const id = messageKeyword[1];\n const token = messageKeyword[2];\n const expiresAt = messageKeyword[3];\n\n const tokenBuffer = Buffer.from(token ?? \"\", \"utf8\");\n const expectedTokenBuffer = Buffer.from(userData.token, \"utf8\");\n\n const senderPhone = parsedData.sendersPhoneNumber.replace(/\\D/g, \"\");\n const phoneBuffer = Buffer.from(senderPhone, \"utf8\");\n const expectedPhoneBuffer = Buffer.from(userData.expectedPhoneNumber, \"utf8\");\n\n const isTokenValid =\n tokenBuffer.length === expectedTokenBuffer.length &&\n crypto.timingSafeEqual(tokenBuffer, expectedTokenBuffer);\n\n const isPhoneValid =\n phoneBuffer.length === expectedPhoneBuffer.length &&\n crypto.timingSafeEqual(phoneBuffer, expectedPhoneBuffer);\n\n if (!isTokenValid || !isPhoneValid || Number(expiresAt) < Date.now()) {\n return {\n success: false,\n message: \"Token is invalid or expired\",\n };\n }\n\n return {\n success: true,\n message: \"Successfully parsed the data\",\n parsedData: parsedData,\n };\n }\n\n public handleWebhookVerification(query: WebhookVerificationQuery): WebhookVerificationResult {\n const mode = query[\"hub.mode\"];\n const token = query[\"hub.verify_token\"];\n const challenge = query[\"hub.challenge\"];\n\n if (mode === \"subscribe\" && token === this.webhookVerifyToken) {\n return { success: true, challenge };\n }\n\n return { success: false };\n }\n}\n\nexport default WhatsAppOtpVerifier;\n","import WhatsAppOtpVerifier from \"./whatsappverifier.js\";\nexport default WhatsAppOtpVerifier;\nexport * from \"./types/index.js\";\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -54,9 +54,11 @@ interface MetaWebhookPayload {
|
|
|
54
54
|
*/
|
|
55
55
|
interface Config {
|
|
56
56
|
businessPhoneNumber: string;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
webhookVerifyToken: string;
|
|
58
|
+
appSecret?: string;
|
|
59
|
+
storage: StorageAdapter;
|
|
60
|
+
defaultTtl?: number;
|
|
61
|
+
defaultTtlSeconds?: number;
|
|
60
62
|
}
|
|
61
63
|
/**
|
|
62
64
|
* Data stored in external driver provided by adapter
|
|
@@ -85,6 +87,7 @@ interface GeneratedOrCode {
|
|
|
85
87
|
interface CreateSessionResult extends GeneratedOrCode {
|
|
86
88
|
id: string;
|
|
87
89
|
token: string;
|
|
90
|
+
expiresAt: number;
|
|
88
91
|
}
|
|
89
92
|
interface VerifyWebhooks {
|
|
90
93
|
rawBody: Buffer;
|
|
@@ -113,10 +116,11 @@ interface WebhookVerificationResult {
|
|
|
113
116
|
challenge?: string;
|
|
114
117
|
}
|
|
115
118
|
|
|
116
|
-
declare class
|
|
119
|
+
declare class WhatsAppOtpVerifier {
|
|
117
120
|
private storage;
|
|
118
121
|
private businessPhoneNumber;
|
|
119
|
-
private
|
|
122
|
+
private webhookVerifyToken;
|
|
123
|
+
private appSecret?;
|
|
120
124
|
private defaultTtl;
|
|
121
125
|
constructor(config: Config);
|
|
122
126
|
private validateTtl;
|
|
@@ -125,4 +129,4 @@ declare class whatsappVerifier {
|
|
|
125
129
|
handleWebhookVerification(query: WebhookVerificationQuery): WebhookVerificationResult;
|
|
126
130
|
}
|
|
127
131
|
|
|
128
|
-
export { type Config, type CreateSessionConfig, type CreateSessionResult, type GeneratedOrCode, type ParsedData, type ParsedDataOutputs, type StorageAdapter, type StorageData, type VerifySession, type VerifyWebhooks, type WebhookVerificationQuery, type WebhookVerificationResult,
|
|
132
|
+
export { type Config, type CreateSessionConfig, type CreateSessionResult, type GeneratedOrCode, type ParsedData, type ParsedDataOutputs, type StorageAdapter, type StorageData, type VerifySession, type VerifyWebhooks, type WebhookVerificationQuery, type WebhookVerificationResult, WhatsAppOtpVerifier as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -54,9 +54,11 @@ interface MetaWebhookPayload {
|
|
|
54
54
|
*/
|
|
55
55
|
interface Config {
|
|
56
56
|
businessPhoneNumber: string;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
webhookVerifyToken: string;
|
|
58
|
+
appSecret?: string;
|
|
59
|
+
storage: StorageAdapter;
|
|
60
|
+
defaultTtl?: number;
|
|
61
|
+
defaultTtlSeconds?: number;
|
|
60
62
|
}
|
|
61
63
|
/**
|
|
62
64
|
* Data stored in external driver provided by adapter
|
|
@@ -85,6 +87,7 @@ interface GeneratedOrCode {
|
|
|
85
87
|
interface CreateSessionResult extends GeneratedOrCode {
|
|
86
88
|
id: string;
|
|
87
89
|
token: string;
|
|
90
|
+
expiresAt: number;
|
|
88
91
|
}
|
|
89
92
|
interface VerifyWebhooks {
|
|
90
93
|
rawBody: Buffer;
|
|
@@ -113,10 +116,11 @@ interface WebhookVerificationResult {
|
|
|
113
116
|
challenge?: string;
|
|
114
117
|
}
|
|
115
118
|
|
|
116
|
-
declare class
|
|
119
|
+
declare class WhatsAppOtpVerifier {
|
|
117
120
|
private storage;
|
|
118
121
|
private businessPhoneNumber;
|
|
119
|
-
private
|
|
122
|
+
private webhookVerifyToken;
|
|
123
|
+
private appSecret?;
|
|
120
124
|
private defaultTtl;
|
|
121
125
|
constructor(config: Config);
|
|
122
126
|
private validateTtl;
|
|
@@ -125,4 +129,4 @@ declare class whatsappVerifier {
|
|
|
125
129
|
handleWebhookVerification(query: WebhookVerificationQuery): WebhookVerificationResult;
|
|
126
130
|
}
|
|
127
131
|
|
|
128
|
-
export { type Config, type CreateSessionConfig, type CreateSessionResult, type GeneratedOrCode, type ParsedData, type ParsedDataOutputs, type StorageAdapter, type StorageData, type VerifySession, type VerifyWebhooks, type WebhookVerificationQuery, type WebhookVerificationResult,
|
|
132
|
+
export { type Config, type CreateSessionConfig, type CreateSessionResult, type GeneratedOrCode, type ParsedData, type ParsedDataOutputs, type StorageAdapter, type StorageData, type VerifySession, type VerifyWebhooks, type WebhookVerificationQuery, type WebhookVerificationResult, WhatsAppOtpVerifier as default };
|
package/dist/index.mjs
CHANGED
|
@@ -62,16 +62,21 @@ function generateKey(phoneNumber) {
|
|
|
62
62
|
// src/whatsappverifier.ts
|
|
63
63
|
var MIN_TTL_SECONDS = 30;
|
|
64
64
|
var MAX_TTL_SECONDS = 3600;
|
|
65
|
-
var
|
|
65
|
+
var WhatsAppOtpVerifier = class {
|
|
66
66
|
storage;
|
|
67
67
|
businessPhoneNumber;
|
|
68
|
-
|
|
68
|
+
webhookVerifyToken;
|
|
69
|
+
appSecret;
|
|
69
70
|
defaultTtl;
|
|
70
71
|
constructor(config) {
|
|
71
72
|
this.storage = config.storage;
|
|
72
73
|
this.businessPhoneNumber = config.businessPhoneNumber;
|
|
73
|
-
this.
|
|
74
|
-
this.
|
|
74
|
+
this.webhookVerifyToken = config.webhookVerifyToken;
|
|
75
|
+
this.appSecret = config.appSecret;
|
|
76
|
+
this.defaultTtl = this.validateTtl(
|
|
77
|
+
config.defaultTtl ?? config.defaultTtlSeconds ?? 180,
|
|
78
|
+
"defaultTtl"
|
|
79
|
+
);
|
|
75
80
|
}
|
|
76
81
|
validateTtl(ttl, label) {
|
|
77
82
|
if (!Number.isFinite(ttl) || ttl < MIN_TTL_SECONDS || ttl > MAX_TTL_SECONDS) {
|
|
@@ -112,14 +117,15 @@ var whatsappVerifier = class {
|
|
|
112
117
|
dataLink,
|
|
113
118
|
deepLink,
|
|
114
119
|
qrLink,
|
|
115
|
-
qrSvg
|
|
120
|
+
qrSvg,
|
|
121
|
+
expiresAt
|
|
116
122
|
};
|
|
117
123
|
}
|
|
118
124
|
async verifySession(params) {
|
|
119
125
|
const rawBody = params.rawBody;
|
|
120
126
|
const jsonData = params.jsonData;
|
|
121
127
|
const signature = params.signature;
|
|
122
|
-
const isSignatureValid = verifyMetaSignature(rawBody, signature, this.
|
|
128
|
+
const isSignatureValid = verifyMetaSignature(rawBody, signature, this.appSecret ?? "");
|
|
123
129
|
if (!isSignatureValid) {
|
|
124
130
|
return {
|
|
125
131
|
success: false,
|
|
@@ -178,13 +184,13 @@ var whatsappVerifier = class {
|
|
|
178
184
|
const mode = query["hub.mode"];
|
|
179
185
|
const token = query["hub.verify_token"];
|
|
180
186
|
const challenge = query["hub.challenge"];
|
|
181
|
-
if (mode === "subscribe" && token === this.
|
|
187
|
+
if (mode === "subscribe" && token === this.webhookVerifyToken) {
|
|
182
188
|
return { success: true, challenge };
|
|
183
189
|
}
|
|
184
190
|
return { success: false };
|
|
185
191
|
}
|
|
186
192
|
};
|
|
187
|
-
var whatsappverifier_default =
|
|
193
|
+
var whatsappverifier_default = WhatsAppOtpVerifier;
|
|
188
194
|
|
|
189
195
|
// src/index.ts
|
|
190
196
|
var index_default = whatsappverifier_default;
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/qr/qrGenerator.ts","../src/webhooks/signature.ts","../src/webhooks/parser.ts","../src/keyGenerator/keyGenerator.ts","../src/whatsappverifier.ts","../src/index.ts"],"names":["crypto"],"mappings":"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","file":"index.mjs","sourcesContent":["import QRCode from \"qrcode\";\nimport { GeneratedOrCode } from \"../types/index.js\";\n\nexport const generateQrFromData = async (\n phoneNumber: string,\n token: string,\n): Promise<GeneratedOrCode> => {\n const encodedText = encodeURIComponent(token);\n const dataLink = `https://wa.me/${phoneNumber}?text=${encodedText}`;\n const deepLink = `whatsapp://send?phone=${phoneNumber}&text=${encodedText}`;\n\n const qrLink = await QRCode.toDataURL(dataLink, {\n width: 300,\n margin: 2,\n errorCorrectionLevel: \"M\",\n });\n\n const qrSvg = await QRCode.toString(dataLink, {\n type: \"svg\",\n margin: 2,\n errorCorrectionLevel: \"M\",\n });\n\n return {\n dataLink: dataLink,\n deepLink: deepLink,\n qrLink: qrLink,\n qrSvg: qrSvg,\n } as GeneratedOrCode;\n};\n","import crypto from \"node:crypto\";\n\nexport const verifyMetaSignature = (\n rawPayload: Buffer,\n signatureHeader: string,\n secretKey: string,\n) => {\n if (!rawPayload || !signatureHeader || !secretKey) return false;\n\n const [algorithm, signature] = signatureHeader.split(\"=\");\n\n const expectedSignature = crypto\n .createHmac(\"sha256\", secretKey)\n .update(rawPayload)\n .digest(\"hex\");\n\n const signatureBuffer = Buffer.from(signature, \"utf8\");\n const expectedBuffer = Buffer.from(expectedSignature, \"utf8\");\n if (signatureBuffer.length !== expectedBuffer.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(signatureBuffer, expectedBuffer);\n};\n","import { ParsedData } from \"../types/index.js\";\nimport { MetaWebhookPayload } from \"../types/internal.js\";\n\nexport const parser = (jsonData: MetaWebhookPayload): ParsedData => {\n try {\n const entry = jsonData?.entry?.[0];\n const change = entry?.changes?.[0];\n const value = change?.value;\n const message = value?.messages?.[0];\n\n if (!message || message.type !== \"text\") return null;\n\n return {\n sendersPhoneNumber: message.from,\n text: message.text.body,\n messageId: message.id,\n timestamp: message.timestamp,\n };\n } catch (error) {\n return null;\n }\n};\n","export function generateKey(phoneNumber: string): string {\n return `wa_verify:${phoneNumber}`;\n}\n","import { generateQrFromData } from \"./qr/qrGenerator.js\";\nimport {\n Config,\n CreateSessionConfig,\n CreateSessionResult,\n StorageAdapter,\n StorageData,\n VerifySession,\n VerifyWebhooks,\n WebhookVerificationQuery,\n WebhookVerificationResult,\n} from \"./types/index.js\";\nimport crypto from \"node:crypto\";\nimport { verifyMetaSignature } from \"./webhooks/signature.js\";\nimport { parser } from \"./webhooks/parser.js\";\nimport { generateKey } from \"./keyGenerator/keyGenerator.js\";\n\nconst MIN_TTL_SECONDS = 30;\nconst MAX_TTL_SECONDS = 3600;\n\nclass whatsappVerifier {\n private storage: StorageAdapter;\n private businessPhoneNumber: string;\n private waVerificationKey: string;\n private defaultTtl: number;\n\n constructor(config: Config) {\n this.storage = config.storage;\n this.businessPhoneNumber = config.businessPhoneNumber;\n this.waVerificationKey = config.waVerificationKey;\n this.defaultTtl = this.validateTtl(config.defaultTtl, \"defaultTtl\");\n }\n\n private validateTtl(ttl: number, label: string): number {\n if (!Number.isFinite(ttl) || ttl < MIN_TTL_SECONDS || ttl > MAX_TTL_SECONDS) {\n throw new RangeError(\n `${label} must be between ${MIN_TTL_SECONDS} and ${MAX_TTL_SECONDS} seconds, got ${ttl}`,\n );\n }\n return ttl;\n }\n\n public async createSession(params: CreateSessionConfig): Promise<CreateSessionResult> {\n const id = crypto.randomUUID();\n const cleanPhone = params.phoneNumber.replace(/\\D/g, \"\");\n const ttl = params.ttl ? this.validateTtl(params.ttl, \"ttl\") : this.defaultTtl;\n const token = crypto.randomInt(100000, 999999).toString();\n const expiresAt = ttl * 1000 + Date.now();\n\n const sessionData: StorageData = {\n sessionId: id,\n expectedPhoneNumber: cleanPhone,\n token: token,\n expiresAt: expiresAt,\n };\n\n const key = generateKey(cleanPhone);\n\n try {\n await this.storage.set(key, JSON.stringify(sessionData), expiresAt);\n } catch (error: unknown) {\n throw new Error(\n `Failed to store session data: ${error instanceof Error ? error.message : error}`,\n );\n }\n\n const message = `VERIFY ${id} ${token} ${expiresAt}`;\n const { dataLink, deepLink, qrLink, qrSvg } = await generateQrFromData(\n this.businessPhoneNumber,\n message,\n );\n\n return {\n id,\n token,\n dataLink,\n deepLink,\n qrLink,\n qrSvg,\n };\n }\n\n public async verifySession(params: VerifyWebhooks): Promise<VerifySession> {\n const rawBody = params.rawBody;\n const jsonData = params.jsonData;\n const signature = params.signature;\n\n const isSignatureValid = verifyMetaSignature(rawBody, signature, this.waVerificationKey);\n if (!isSignatureValid) {\n return {\n success: false,\n message: \"Invalid webhook signature\",\n };\n }\n\n const parsedData = parser(jsonData);\n if (!parsedData) {\n return {\n success: false,\n message: \"Invalid or unsupported webhook message\",\n };\n }\n\n const key = generateKey(parsedData.sendersPhoneNumber.replace(/\\D/g, \"\"));\n let userData;\n\n try {\n const data = await this.storage.get(key);\n if (!data) {\n return {\n success: false,\n message: \"Session data not found\",\n };\n }\n\n userData = JSON.parse(data) as StorageData;\n await this.storage.delete(key);\n } catch (error: unknown) {\n return {\n success: false,\n message: `Failed to get/delete session data: ${error instanceof Error ? error.message : error}`,\n };\n }\n\n const messageKeyword = parsedData.text.trim().split(\" \");\n const id = messageKeyword[1];\n const token = messageKeyword[2];\n const expiresAt = messageKeyword[3];\n\n const tokenBuffer = Buffer.from(token ?? \"\", \"utf8\");\n const expectedTokenBuffer = Buffer.from(userData.token, \"utf8\");\n\n const senderPhone = parsedData.sendersPhoneNumber.replace(/\\D/g, \"\");\n const phoneBuffer = Buffer.from(senderPhone, \"utf8\");\n const expectedPhoneBuffer = Buffer.from(userData.expectedPhoneNumber, \"utf8\");\n\n const isTokenValid =\n tokenBuffer.length === expectedTokenBuffer.length &&\n crypto.timingSafeEqual(tokenBuffer, expectedTokenBuffer);\n\n const isPhoneValid =\n phoneBuffer.length === expectedPhoneBuffer.length &&\n crypto.timingSafeEqual(phoneBuffer, expectedPhoneBuffer);\n\n if (!isTokenValid || !isPhoneValid || Number(expiresAt) < Date.now()) {\n return {\n success: false,\n message: \"Token is invalid or expired\",\n };\n }\n\n return {\n success: true,\n message: \"Successfully parsed the data\",\n parsedData: parsedData,\n };\n }\n\n public handleWebhookVerification(query: WebhookVerificationQuery): WebhookVerificationResult {\n const mode = query[\"hub.mode\"];\n const token = query[\"hub.verify_token\"];\n const challenge = query[\"hub.challenge\"];\n\n if (mode === \"subscribe\" && token === this.waVerificationKey) {\n return { success: true, challenge };\n }\n\n return { success: false };\n }\n}\n\nexport default whatsappVerifier;\n","import whatsappVerifier from \"./whatsappverifier.js\";\n\nexport default whatsappVerifier;\nexport * from \"./types/index.js\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/qr/qrGenerator.ts","../src/webhooks/signature.ts","../src/webhooks/parser.ts","../src/keyGenerator/keyGenerator.ts","../src/whatsappverifier.ts","../src/index.ts"],"names":["crypto"],"mappings":"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","file":"index.mjs","sourcesContent":["import QRCode from \"qrcode\";\nimport { GeneratedOrCode } from \"../types/index.js\";\n\nexport const generateQrFromData = async (\n phoneNumber: string,\n token: string,\n): Promise<GeneratedOrCode> => {\n const encodedText = encodeURIComponent(token);\n const dataLink = `https://wa.me/${phoneNumber}?text=${encodedText}`;\n const deepLink = `whatsapp://send?phone=${phoneNumber}&text=${encodedText}`;\n\n const qrLink = await QRCode.toDataURL(dataLink, {\n width: 300,\n margin: 2,\n errorCorrectionLevel: \"M\",\n });\n\n const qrSvg = await QRCode.toString(dataLink, {\n type: \"svg\",\n margin: 2,\n errorCorrectionLevel: \"M\",\n });\n\n return {\n dataLink: dataLink,\n deepLink: deepLink,\n qrLink: qrLink,\n qrSvg: qrSvg,\n } as GeneratedOrCode;\n};\n","import crypto from \"node:crypto\";\n\nexport const verifyMetaSignature = (\n rawPayload: Buffer,\n signatureHeader: string,\n secretKey: string,\n) => {\n if (!rawPayload || !signatureHeader || !secretKey) return false;\n\n const [algorithm, signature] = signatureHeader.split(\"=\");\n\n const expectedSignature = crypto\n .createHmac(\"sha256\", secretKey)\n .update(rawPayload)\n .digest(\"hex\");\n\n const signatureBuffer = Buffer.from(signature, \"utf8\");\n const expectedBuffer = Buffer.from(expectedSignature, \"utf8\");\n if (signatureBuffer.length !== expectedBuffer.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(signatureBuffer, expectedBuffer);\n};\n","import { ParsedData } from \"../types/index.js\";\nimport { MetaWebhookPayload } from \"../types/internal.js\";\n\nexport const parser = (jsonData: MetaWebhookPayload): ParsedData => {\n try {\n const entry = jsonData?.entry?.[0];\n const change = entry?.changes?.[0];\n const value = change?.value;\n const message = value?.messages?.[0];\n\n if (!message || message.type !== \"text\") return null;\n\n return {\n sendersPhoneNumber: message.from,\n text: message.text.body,\n messageId: message.id,\n timestamp: message.timestamp,\n };\n } catch (error) {\n return null;\n }\n};\n","export function generateKey(phoneNumber: string): string {\n return `wa_verify:${phoneNumber}`;\n}\n","import { generateQrFromData } from \"./qr/qrGenerator.js\";\nimport {\n Config,\n CreateSessionConfig,\n CreateSessionResult,\n StorageAdapter,\n StorageData,\n VerifySession,\n VerifyWebhooks,\n WebhookVerificationQuery,\n WebhookVerificationResult,\n} from \"./types/index.js\";\nimport crypto from \"node:crypto\";\nimport { verifyMetaSignature } from \"./webhooks/signature.js\";\nimport { parser } from \"./webhooks/parser.js\";\nimport { generateKey } from \"./keyGenerator/keyGenerator.js\";\n\nconst MIN_TTL_SECONDS = 30;\nconst MAX_TTL_SECONDS = 3600;\n\nclass WhatsAppOtpVerifier {\n private storage: StorageAdapter;\n private businessPhoneNumber: string;\n private webhookVerifyToken: string;\n private appSecret?: string; \n private defaultTtl: number;\n\n constructor(config: Config) {\n this.storage = config.storage;\n this.businessPhoneNumber = config.businessPhoneNumber;\n this.webhookVerifyToken = config.webhookVerifyToken;\n this.appSecret = config.appSecret;\n this.defaultTtl = this.validateTtl(\n config.defaultTtl ?? config.defaultTtlSeconds ?? 180,\n \"defaultTtl\",\n );\n }\n\n private validateTtl(ttl: number, label: string): number {\n if (!Number.isFinite(ttl) || ttl < MIN_TTL_SECONDS || ttl > MAX_TTL_SECONDS) {\n throw new RangeError(\n `${label} must be between ${MIN_TTL_SECONDS} and ${MAX_TTL_SECONDS} seconds, got ${ttl}`,\n );\n }\n return ttl;\n }\n\n public async createSession(params: CreateSessionConfig): Promise<CreateSessionResult> {\n const id = crypto.randomUUID();\n const cleanPhone = params.phoneNumber.replace(/\\D/g, \"\");\n const ttl = params.ttl ? this.validateTtl(params.ttl, \"ttl\") : this.defaultTtl;\n const token = crypto.randomInt(100000, 999999).toString();\n const expiresAt = ttl * 1000 + Date.now();\n\n const sessionData: StorageData = {\n sessionId: id,\n expectedPhoneNumber: cleanPhone,\n token: token,\n expiresAt: expiresAt,\n };\n\n const key = generateKey(cleanPhone);\n\n try {\n await this.storage.set(key, JSON.stringify(sessionData), expiresAt);\n } catch (error: unknown) {\n throw new Error(\n `Failed to store session data: ${error instanceof Error ? error.message : error}`,\n );\n }\n\n const message = `VERIFY ${id} ${token} ${expiresAt}`;\n const { dataLink, deepLink, qrLink, qrSvg } = await generateQrFromData(\n this.businessPhoneNumber,\n message,\n );\n\n return {\n id,\n token,\n dataLink,\n deepLink,\n qrLink,\n qrSvg,\n expiresAt,\n };\n }\n\n public async verifySession(params: VerifyWebhooks): Promise<VerifySession> {\n const rawBody = params.rawBody;\n const jsonData = params.jsonData;\n const signature = params.signature;\n\n const isSignatureValid = verifyMetaSignature(rawBody, signature, this.appSecret ?? \"\");\n if (!isSignatureValid) {\n return {\n success: false,\n message: \"Invalid webhook signature\",\n };\n }\n\n const parsedData = parser(jsonData);\n if (!parsedData) {\n return {\n success: false,\n message: \"Invalid or unsupported webhook message\",\n };\n }\n\n const key = generateKey(parsedData.sendersPhoneNumber.replace(/\\D/g, \"\"));\n let userData;\n\n try {\n const data = await this.storage.get(key);\n if (!data) {\n return {\n success: false,\n message: \"Session data not found\",\n };\n }\n\n userData = JSON.parse(data) as StorageData;\n await this.storage.delete(key);\n } catch (error: unknown) {\n return {\n success: false,\n message: `Failed to get/delete session data: ${error instanceof Error ? error.message : error}`,\n };\n }\n\n const messageKeyword = parsedData.text.trim().split(\" \");\n const id = messageKeyword[1];\n const token = messageKeyword[2];\n const expiresAt = messageKeyword[3];\n\n const tokenBuffer = Buffer.from(token ?? \"\", \"utf8\");\n const expectedTokenBuffer = Buffer.from(userData.token, \"utf8\");\n\n const senderPhone = parsedData.sendersPhoneNumber.replace(/\\D/g, \"\");\n const phoneBuffer = Buffer.from(senderPhone, \"utf8\");\n const expectedPhoneBuffer = Buffer.from(userData.expectedPhoneNumber, \"utf8\");\n\n const isTokenValid =\n tokenBuffer.length === expectedTokenBuffer.length &&\n crypto.timingSafeEqual(tokenBuffer, expectedTokenBuffer);\n\n const isPhoneValid =\n phoneBuffer.length === expectedPhoneBuffer.length &&\n crypto.timingSafeEqual(phoneBuffer, expectedPhoneBuffer);\n\n if (!isTokenValid || !isPhoneValid || Number(expiresAt) < Date.now()) {\n return {\n success: false,\n message: \"Token is invalid or expired\",\n };\n }\n\n return {\n success: true,\n message: \"Successfully parsed the data\",\n parsedData: parsedData,\n };\n }\n\n public handleWebhookVerification(query: WebhookVerificationQuery): WebhookVerificationResult {\n const mode = query[\"hub.mode\"];\n const token = query[\"hub.verify_token\"];\n const challenge = query[\"hub.challenge\"];\n\n if (mode === \"subscribe\" && token === this.webhookVerifyToken) {\n return { success: true, challenge };\n }\n\n return { success: false };\n }\n}\n\nexport default WhatsAppOtpVerifier;\n","import WhatsAppOtpVerifier from \"./whatsappverifier.js\";\nexport default WhatsAppOtpVerifier;\nexport * from \"./types/index.js\";\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "angelos",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Frictionless WhatsApp QR-based phone verification engine for Node.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -18,6 +18,14 @@
|
|
|
18
18
|
"README.md",
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/hartej46/angelos.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/hartej46/angelos#readme",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/hartej46/angelos/issues"
|
|
28
|
+
},
|
|
21
29
|
"scripts": {
|
|
22
30
|
"dev": "tsup --watch",
|
|
23
31
|
"build": "tsup",
|