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 CHANGED
@@ -1,3 +1,187 @@
1
- # Angelos
1
+ # @wa-auth/verifier
2
2
 
3
- This is an npm package.
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 whatsappVerifier = class {
72
+ var WhatsAppOtpVerifier = class {
73
73
  storage;
74
74
  businessPhoneNumber;
75
- waVerificationKey;
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.waVerificationKey = config.waVerificationKey;
81
- this.defaultTtl = this.validateTtl(config.defaultTtl, "defaultTtl");
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.waVerificationKey);
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.waVerificationKey) {
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 = whatsappVerifier;
200
+ var whatsappverifier_default = WhatsAppOtpVerifier;
195
201
 
196
202
  // src/index.ts
197
203
  var index_default = whatsappverifier_default;
@@ -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
- waVerificationKey: string;
58
- storage: any;
59
- defaultTtl: number;
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 whatsappVerifier {
119
+ declare class WhatsAppOtpVerifier {
117
120
  private storage;
118
121
  private businessPhoneNumber;
119
- private waVerificationKey;
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, whatsappVerifier as default };
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
- waVerificationKey: string;
58
- storage: any;
59
- defaultTtl: number;
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 whatsappVerifier {
119
+ declare class WhatsAppOtpVerifier {
117
120
  private storage;
118
121
  private businessPhoneNumber;
119
- private waVerificationKey;
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, whatsappVerifier as default };
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 whatsappVerifier = class {
65
+ var WhatsAppOtpVerifier = class {
66
66
  storage;
67
67
  businessPhoneNumber;
68
- waVerificationKey;
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.waVerificationKey = config.waVerificationKey;
74
- this.defaultTtl = this.validateTtl(config.defaultTtl, "defaultTtl");
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.waVerificationKey);
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.waVerificationKey) {
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 = whatsappVerifier;
193
+ var whatsappverifier_default = WhatsAppOtpVerifier;
188
194
 
189
195
  // src/index.ts
190
196
  var index_default = whatsappverifier_default;
@@ -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.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",