@power-rent/phone-validation-adapter 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Power Rent
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,459 @@
1
+ # Phone Validation Adapter
2
+
3
+ REST API adapter for phone number validation using Twilio Lookup API. This project was created to solve a critical issue: legacy applications running on Node.js < 14 cannot directly install the Twilio SDK due to version incompatibility. This adapter provides a simple REST interface that allows legacy systems to validate phone numbers by calling this service, eliminating the need to upgrade their entire Node.js infrastructure.
4
+
5
+ ## Setup
6
+
7
+ ### Prerequisites
8
+
9
+ - Node.js >= 18.0.0
10
+ - Twilio Account with API credentials
11
+
12
+ ### Installation
13
+
14
+ ```bash
15
+ npm install
16
+ ```
17
+
18
+ ### Environment Variables
19
+
20
+ Create a `.env` file in the root directory:
21
+
22
+ ```
23
+ TWILIO_ACCOUNT_SID=your_account_sid
24
+ TWILIO_AUTH_TOKEN=your_auth_token
25
+ PORT=3000
26
+ ```
27
+
28
+ **Important:** The `TWILIO_ACCOUNT_SID` and `TWILIO_AUTH_TOKEN` are required. Without them, the validation service will return HTTP 503.
29
+
30
+ ### API Key Configuration
31
+
32
+ All requests to the validation endpoint require an API key and project name for authentication. API keys are stored in `src/validApiKeys.ts`:
33
+
34
+ ```typescript
35
+ const validApiKeys: Record<string, string> = {
36
+ RLC: process.env.API_KEY_RLC || '',
37
+ TJS: process.env.API_KEY_TJS || ''
38
+ };
39
+ ```
40
+
41
+ **Project Names:**
42
+ - `RLC` - Power Rent Rental Company
43
+ - `TJS` - TJ Services
44
+
45
+ **To add a new project:**
46
+
47
+ 1. Add environment variable to `.env`:
48
+ ```
49
+ API_KEY_NEW_PROJECT="your-secret-api-key"
50
+ ```
51
+
52
+ 2. Update `src/validApiKeys.ts`:
53
+ ```typescript
54
+ const validApiKeys: Record<string, string> = {
55
+ RLC: process.env.API_KEY_RLC || '',
56
+ TJS: process.env.API_KEY_TJS || '',
57
+ NEW_PROJECT: process.env.API_KEY_NEW_PROJECT || ''
58
+ };
59
+ ```
60
+
61
+ 3. Rebuild and restart:
62
+ ```bash
63
+ npm run build
64
+ npm run start
65
+ ```
66
+
67
+ **Important Security Notes:**
68
+ - Never commit actual API keys to version control
69
+ - Store keys in environment variables or a secrets manager
70
+ - Each client application should have its own unique key
71
+ - Rotate keys regularly (every 90 days recommended)
72
+
73
+ ## API Endpoints
74
+
75
+ ### POST /validate
76
+
77
+ Validates a phone number using Twilio Lookup API.
78
+
79
+ **Authentication Required:** `x-api-key` and `x-project-name` headers with valid credentials.
80
+
81
+ #### Request
82
+
83
+ ```bash
84
+ curl -X POST http://localhost:3000/validate \
85
+ -H "Content-Type: application/json" \
86
+ -H "x-api-key: apple-blossom-elephant-rocket" \
87
+ -H "x-project-name: RLC" \
88
+ -d '{"phoneNumber": "+1234567890"}'
89
+ ```
90
+
91
+ Request body:
92
+
93
+ ```json
94
+ {
95
+ "phoneNumber": "+1234567890"
96
+ }
97
+ ```
98
+
99
+ Request headers:
100
+
101
+ | Header | Value | Required | Description |
102
+ |--------|-------|----------|-------------|
103
+ | `x-api-key` | API Key | Yes | Project-specific API key for authentication |
104
+ | `x-project-name` | Project Name | Yes | Project identifier (RLC or TJS) |
105
+ | `Content-Type` | application/json | Yes | Request content type |
106
+
107
+ #### Response (Success - HTTP 200)
108
+
109
+ ```json
110
+ {
111
+ "success": true,
112
+ "isValid": true
113
+ }
114
+ ```
115
+
116
+ #### Response (Invalid Number - HTTP 200)
117
+
118
+ ```json
119
+ {
120
+ "success": true,
121
+ "isValid": false
122
+ }
123
+ ```
124
+
125
+ #### Response (Unauthorized - HTTP 401)
126
+
127
+ Returned when API key or project name is missing or invalid:
128
+
129
+ ```json
130
+ {
131
+ "success": false,
132
+ "error": "Invalid API key"
133
+ }
134
+ ```
135
+
136
+ Or:
137
+
138
+ ```json
139
+ {
140
+ "success": false,
141
+ "error": "Invalid project name"
142
+ }
143
+ ```
144
+
145
+ #### Response (Client Error - HTTP 400)
146
+
147
+ ```json
148
+ {
149
+ "success": false,
150
+ "error": "phoneNumber is required and must be a non-empty string"
151
+ }
152
+ ```
153
+
154
+ #### Response (Service Unavailable - HTTP 503)
155
+
156
+ Returned when Twilio credentials are not configured:
157
+
158
+ ```json
159
+ {
160
+ "success": false,
161
+ "error": "Validation service is temporarily unavailable"
162
+ }
163
+ ```
164
+
165
+ #### Response (Server Error - HTTP 500)
166
+
167
+ Returned when Twilio API fails or times out:
168
+
169
+ ```json
170
+ {
171
+ "success": false,
172
+ "error": "Phone validation failed. Please try again later."
173
+ }
174
+ ```
175
+
176
+ ## Error Handling Flow
177
+
178
+ This diagram shows how errors are handled at different stages:
179
+
180
+ ```
181
+ POST /validate
182
+
183
+ ├─ API Key Validation
184
+ │ ├─ x-project-name header missing?
185
+ │ │ └─ HTTP 401 (Unauthorized)
186
+ │ │ └─ Client error: invalid project name
187
+ │ │
188
+ │ ├─ x-project-name exists in whitelist?
189
+ │ │ └─ Continue to x-api-key validation
190
+ │ │
191
+ │ ├─ x-api-key header missing?
192
+ │ │ └─ HTTP 401 (Unauthorized)
193
+ │ │ └─ Client error: invalid API key
194
+ │ │
195
+ │ └─ x-api-key matches project?
196
+ │ └─ Continue to Input validation
197
+
198
+ ├─ Input Validation
199
+ │ ├─ Missing or empty phoneNumber?
200
+ │ │ └─ HTTP 400 (Bad Request)
201
+ │ │ └─ Client error: invalid input
202
+ │ │
203
+ │ └─ phoneNumber is valid string?
204
+ │ └─ Continue to Twilio validation
205
+
206
+ ├─ Configuration Check (twilioLookupValidation.ts)
207
+ │ ├─ TWILIO_ACCOUNT_SID missing?
208
+ │ │ ├─ Throw ConfigurationError
209
+ │ │ ├─ Log to Sentry
210
+ │ │ └─ server.ts catches it
211
+ │ │ └─ HTTP 503 (Service Unavailable)
212
+ │ │ └─ User sees: "Service temporarily unavailable"
213
+ │ │
214
+ │ ├─ TWILIO_AUTH_TOKEN missing?
215
+ │ │ ├─ Throw ConfigurationError
216
+ │ │ ├─ Log to Sentry
217
+ │ │ └─ server.ts catches it
218
+ │ │ └─ HTTP 503 (Service Unavailable)
219
+ │ │
220
+ │ └─ Credentials OK?
221
+ │ └─ Call Twilio API with 5s timeout
222
+
223
+ ├─ Twilio API Call
224
+ │ ├─ Request times out (> 5s)?
225
+ │ │ ├─ Throw ValidationError
226
+ │ │ ├─ Log to Sentry
227
+ │ │ └─ server.ts catches it
228
+ │ │ └─ HTTP 500 (Internal Server Error)
229
+ │ │ └─ User sees: "Validation failed, try again"
230
+ │ │
231
+ │ ├─ Twilio API returns error?
232
+ │ │ ├─ Throw ValidationError
233
+ │ │ ├─ Log to Sentry
234
+ │ │ └─ server.ts catches it
235
+ │ │ └─ HTTP 500 (Internal Server Error)
236
+ │ │
237
+ │ ├─ Phone is valid?
238
+ │ │ └─ Return true
239
+ │ │ └─ HTTP 200 + isValid: true
240
+ │ │ └─ User can submit form
241
+ │ │
242
+ │ └─ Phone is invalid?
243
+ │ └─ Return false
244
+ │ └─ HTTP 200 + isValid: false
245
+ │ └─ User sees validation error message
246
+
247
+ └─ Error Logging (All paths)
248
+ └─ Sentry with error type tag
249
+ ├─ ConfigurationError → errorType: 'configuration'
250
+ └─ ValidationError → errorType: 'validation'
251
+ ```
252
+
253
+ ## Key Design Decisions
254
+
255
+ ### 1. Explicit Error Types
256
+
257
+ Two custom error classes distinguish between different failure modes:
258
+
259
+ - **ConfigurationError**: Missing Twilio credentials (HTTP 503)
260
+ - **ValidationError**: Twilio API failures or timeouts (HTTP 500)
261
+
262
+ This prevents silent failures where configuration problems would be masked as valid numbers.
263
+
264
+ ### 2. Timeout Protection
265
+
266
+ Twilio API calls have a 5-second timeout. If exceeded:
267
+ - ValidationError is thrown
268
+ - HTTP 500 is returned
269
+ - User is informed of the failure
270
+
271
+ ### 3. No Silent Failures
272
+
273
+ Previous implementation returned `true` for missing credentials. This caused:
274
+ - Users to believe their phone was validated
275
+ - Form submissions with invalid phone numbers
276
+ - Data quality issues
277
+
278
+ Current implementation:
279
+ - Fails explicitly with HTTP 503
280
+ - Informs users the service is unavailable
281
+ - Prevents invalid data submission
282
+
283
+ ## Architecture & Security
284
+
285
+ This adapter is designed as a **backend microservice** that bridges the gap between legacy applications and Twilio. Understanding the architecture is critical for proper deployment and security.
286
+
287
+ ### Client-Server Architecture
288
+
289
+ ```
290
+ ┌─────────────────────────────────┐
291
+ │ Client Application │
292
+ │ (Frontend/Backend/Legacy app) │
293
+ └────────────┬────────────────────┘
294
+ │ HTTP POST /validate
295
+ │ with x-api-key header
296
+
297
+ ┌─────────────────────────────────────────────┐
298
+ │ Phone Validation Adapter │
299
+ │ (this service) │
300
+ │ ✓ Validates API key │
301
+ │ ✓ Validates phone number format │
302
+ │ ✓ Calls Twilio API │
303
+ │ ✓ Returns validation result │
304
+ └────────────┬────────────────────────────────┘
305
+
306
+
307
+ ┌───────────────────┐
308
+ │ Twilio API │
309
+ └───────────────────┘
310
+ ```
311
+
312
+ ### Backend Responsibilities
313
+
314
+ The backend service (this adapter) is responsible for:
315
+
316
+ 1. **Store API Keys Securely**
317
+
318
+ API keys should **never** be hardcoded in production. Instead, use one of these approaches:
319
+
320
+ - **Environment Variables** (recommended for simple deployments):
321
+ ```bash
322
+ VALID_API_KEYS=key1,key2,key3
323
+ ```
324
+
325
+ - **Secrets Manager** (AWS Secrets Manager, HashiCorp Vault, etc.):
326
+ ```typescript
327
+ // Pseudo-code
328
+ const keys = await secretsManager.getSecret('phone-validation-keys');
329
+ ```
330
+
331
+ - **Database** (for dynamic key management):
332
+ ```typescript
333
+ // Pseudo-code
334
+ const keys = await database.query('SELECT api_key FROM valid_keys WHERE active = true');
335
+ ```
336
+
337
+ 2. **Issue Keys to Clients**
338
+
339
+ Provide a secure endpoint where authenticated clients can request API keys:
340
+
341
+ ```typescript
342
+ // Pseudo-code example
343
+ app.post('/api-keys/request', (req, res) => {
344
+ // Verify client identity (JWT, OAuth, mTLS, etc.)
345
+ // Generate or retrieve API key
346
+ // Return key to client
347
+ res.json({ apiKey: 'generated-key' });
348
+ });
349
+ ```
350
+
351
+ **Important:** This endpoint should:
352
+ - Require strong authentication (JWT, OAuth, mTLS)
353
+ - Rate limit requests
354
+ - Log all key requests
355
+ - Expire keys automatically
356
+ - Allow key rotation
357
+
358
+ 3. **Validate Keys Before Processing**
359
+
360
+ Always verify API keys on the server before calling Twilio:
361
+
362
+ ```typescript
363
+ // Current implementation does this via middleware
364
+ const authenticateApiKey = (req, res, next) => {
365
+ const apiKey = req.headers['x-api-key'];
366
+ const projectName = req.headers['x-project-name'];
367
+
368
+ if (!checkIfKeyExists(projectName)) {
369
+ res.status(401).json({ error: 'Invalid project name' });
370
+ return;
371
+ }
372
+
373
+ if (!isValidApiKey({ key: apiKey, projectName })) {
374
+ res.status(401).json({ error: 'Invalid API key' });
375
+ return;
376
+ }
377
+ next();
378
+ };
379
+ ```
380
+
381
+ Benefits:
382
+ - Keys never exposed to clients
383
+ - Project-specific key validation
384
+ - Prevents unauthorized Twilio API calls
385
+ - Reduces costs (Twilio charges per request)
386
+ - Enables per-project rate limiting
387
+ - Audit trail for all validation attempts
388
+
389
+ ### Frontend/Client Responsibilities
390
+
391
+ Client applications should:
392
+
393
+ 1. **Never hardcode API keys** in frontend code
394
+ 2. **Request keys from backend** via secure endpoint
395
+ 3. **Pass keys in request headers** (`x-api-key`)
396
+ 4. **Handle 401 responses** gracefully
397
+ 5. **Implement retry logic** for transient failures
398
+
399
+ Example client usage:
400
+
401
+ ```typescript
402
+ // Get API key from backend (must be authenticated)
403
+ const apiKey = await fetch('/api/phone-validation-key', {
404
+ headers: { Authorization: 'Bearer ' + userToken }
405
+ }).then(r => r.json()).then(r => r.apiKey);
406
+
407
+ // Get project name from backend config or user settings
408
+ const projectName = 'RLC'; // or 'TJS'
409
+
410
+ // Use key to validate phone
411
+ const result = await fetch('https://validator-service.example.com/validate', {
412
+ method: 'POST',
413
+ headers: {
414
+ 'Content-Type': 'application/json',
415
+ 'x-api-key': apiKey,
416
+ 'x-project-name': projectName
417
+ },
418
+ body: JSON.stringify({ phoneNumber: '+1234567890' })
419
+ });
420
+ ```
421
+
422
+ ### Security Best Practices
423
+
424
+ 1. **Rotate API Keys Regularly** - Change keys every 90 days
425
+ 2. **Use HTTPS** - All communication must be encrypted
426
+ 3. **Rate Limit** - Prevent abuse of the validation endpoint
427
+ 4. **Monitor Usage** - Track validation requests and costs
428
+ 5. **Separate Credentials** - Twilio keys ≠ Adapter keys
429
+ 6. **Audit Logging** - Log all API key usage
430
+ 7. **Principle of Least Privilege** - Give clients minimum permissions needed
431
+
432
+ ## Development
433
+
434
+ ### Start Development Server
435
+
436
+ ```bash
437
+ npm run dev
438
+ ```
439
+
440
+ Server runs on `http://localhost:3000` by default.
441
+
442
+ ### Build
443
+
444
+ ```bash
445
+ npm run build
446
+ ```
447
+
448
+ Output goes to `dist/` directory.
449
+
450
+ ### Testing
451
+
452
+ ```bash
453
+ npm run lint
454
+ npm run format:check
455
+ ```
456
+
457
+ ## License
458
+
459
+ MIT
@@ -0,0 +1 @@
1
+ import "dotenv/config";
package/dist/server.js ADDED
@@ -0,0 +1,76 @@
1
+ import "dotenv/config";
2
+ import express from "express";
3
+ import { twilioLookupValidation } from "./twilioLookupValidation.js";
4
+ import isValidApiKey, { checkIfKeyExists } from "./utils.js";
5
+ const app = express();
6
+ const PORT = process.env.PORT || 3000;
7
+ app.use(express.json());
8
+ const authenticateApiKey = (req, res, next) => {
9
+ const apiKey = req.headers["x-api-key"];
10
+ const projectName = req.headers["x-project-name"];
11
+ if (!checkIfKeyExists(projectName)) {
12
+ res.status(401).json({
13
+ success: false,
14
+ error: "Invalid project name"
15
+ });
16
+ return;
17
+ }
18
+ if (!isValidApiKey({ key: apiKey, projectName })) {
19
+ res.status(401).json({
20
+ success: false,
21
+ error: "Invalid API key"
22
+ });
23
+ return;
24
+ }
25
+ next();
26
+ };
27
+ const validatePhoneNumber = (req, res, next) => {
28
+ const body = req.body;
29
+ const phoneNumber = body?.phoneNumber;
30
+ if (!phoneNumber || typeof phoneNumber !== "string" || phoneNumber.trim().length === 0) {
31
+ res.status(400).json({
32
+ success: false,
33
+ error: "phoneNumber is required and must be a non-empty string"
34
+ });
35
+ return;
36
+ }
37
+ next();
38
+ };
39
+ app.post("/validate", authenticateApiKey, validatePhoneNumber, async (req, res) => {
40
+ try {
41
+ const body = req.body;
42
+ const phoneNumber = body.phoneNumber;
43
+ const isValid = await twilioLookupValidation(phoneNumber);
44
+ res.json({
45
+ success: true,
46
+ isValid
47
+ });
48
+ }
49
+ catch (error) {
50
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
51
+ const errorName = error instanceof Error ? error.name : "Error";
52
+ console.error("Error in /validate route:", { name: errorName, message: errorMessage });
53
+ if (errorName === "ConfigurationError") {
54
+ res.status(503).json({
55
+ success: false,
56
+ error: "Validation service is temporarily unavailable"
57
+ });
58
+ return;
59
+ }
60
+ res.status(500).json({
61
+ success: false,
62
+ error: "Phone validation failed. Please try again later."
63
+ });
64
+ }
65
+ });
66
+ app.use((err, _req, res, _next) => {
67
+ console.error("Middleware error:", err);
68
+ res.status(400).json({
69
+ success: false,
70
+ error: "Invalid JSON in request body"
71
+ });
72
+ });
73
+ app.listen(PORT, () => {
74
+ console.log(`Server is running on port ${PORT}`);
75
+ });
76
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare const twilioLookupValidation: (phoneNumber: string) => Promise<boolean>;
@@ -0,0 +1,46 @@
1
+ import twilio from "twilio";
2
+ import * as Sentry from "@sentry/node";
3
+ class ConfigurationError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "ConfigurationError";
7
+ }
8
+ }
9
+ class ValidationError extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = "ValidationError";
13
+ }
14
+ }
15
+ export const twilioLookupValidation = async (phoneNumber) => {
16
+ try {
17
+ const accountSid = process.env.TWILIO_ACCOUNT_SID;
18
+ const authToken = process.env.TWILIO_AUTH_TOKEN;
19
+ if (!accountSid || !authToken) {
20
+ const error = new ConfigurationError("TWILIO_ACCOUNT_SID or TWILIO_AUTH_TOKEN is not set");
21
+ console.error("ConfigurationError in twilioLookupValidation", error);
22
+ Sentry.captureException(error, {
23
+ tags: { errorType: "configuration", service: "twilio" }
24
+ });
25
+ throw error;
26
+ }
27
+ const client = twilio(accountSid, authToken);
28
+ const phone = phoneNumber.trim();
29
+ const response = await client.lookups.v2.phoneNumbers(phone).fetch();
30
+ return response.valid;
31
+ }
32
+ catch (error) {
33
+ if (error instanceof ConfigurationError) {
34
+ throw error;
35
+ }
36
+ const validationError = error instanceof ValidationError
37
+ ? error
38
+ : new ValidationError(error instanceof Error ? error.message : "Unknown error during phone validation");
39
+ console.error("ValidationError in twilioLookupValidation", validationError);
40
+ Sentry.captureException(validationError, {
41
+ tags: { errorType: "validation", service: "twilio" }
42
+ });
43
+ throw validationError;
44
+ }
45
+ };
46
+ //# sourceMappingURL=twilioLookupValidation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"twilioLookupValidation.js","sourceRoot":"","sources":["../src/twilioLookupValidation.ts"],"names":[],"mappings":"AAEA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;AAkBvC,MAAM,kBAAmB,SAAQ,KAAK;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED,MAAM,eAAgB,SAAQ,KAAK;IACjC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,KAAK,EAAE,WAAmB,EAAoB,EAAE;IACpF,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;QAClD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;QAEhD,IAAI,CAAC,UAAU,IAAI,CAAC,SAAS,EAAE,CAAC;YAC9B,MAAM,KAAK,GAAG,IAAI,kBAAkB,CAAC,oDAAoD,CAAC,CAAC;YAE3F,OAAO,CAAC,KAAK,CAAC,8CAA8C,EAAE,KAAK,CAAC,CAAC;YACrE,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE;gBAC7B,IAAI,EAAE,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE;aACxD,CAAC,CAAC;YACH,MAAM,KAAK,CAAC;QACd,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC;QAEjC,MAAM,QAAQ,GAAyB,MAAM,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC;QAE3F,OAAO,QAAQ,CAAC,KAAK,CAAC;IACxB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,kBAAkB,EAAE,CAAC;YACxC,MAAM,KAAK,CAAC;QACd,CAAC;QAED,MAAM,eAAe,GACnB,KAAK,YAAY,eAAe;YAC9B,CAAC,CAAC,KAAK;YACP,CAAC,CAAC,IAAI,eAAe,CACjB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,uCAAuC,CACjF,CAAC;QAGR,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,eAAe,CAAC,CAAC;QAC5E,MAAM,CAAC,gBAAgB,CAAC,eAAe,EAAE;YACvC,IAAI,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE;SACrD,CAAC,CAAC;QACH,MAAM,eAAe,CAAC;IACxB,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ export declare const checkIfKeyExists: (key: string) => boolean;
2
+ type Args = {
3
+ key: string;
4
+ projectName: string;
5
+ };
6
+ declare const isValidApiKey: ({ key, projectName }: Args) => boolean;
7
+ export default isValidApiKey;
package/dist/utils.js ADDED
@@ -0,0 +1,14 @@
1
+ import validApiKeys from "./validApiKeys.js";
2
+ export const checkIfKeyExists = (key) => {
3
+ return Object.keys(validApiKeys).includes(key);
4
+ };
5
+ const isValidApiKey = ({ key, projectName }) => {
6
+ if (!key || !projectName)
7
+ return false;
8
+ const isValidProjectName = Object.keys(validApiKeys).includes(projectName);
9
+ if (!isValidProjectName)
10
+ return false;
11
+ return validApiKeys[projectName] === key;
12
+ };
13
+ export default isValidApiKey;
14
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,mBAAmB,CAAC;AAE7C,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,GAAW,EAAW,EAAE;IACvD,OAAO,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACjD,CAAC,CAAC;AAOF,MAAM,aAAa,GAAG,CAAC,EAAE,GAAG,EAAE,WAAW,EAAQ,EAAW,EAAE;IAC5D,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IACvC,MAAM,kBAAkB,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC3E,IAAI,CAAC,kBAAkB;QAAE,OAAO,KAAK,CAAC;IACtC,OAAO,YAAY,CAAC,WAAW,CAAC,KAAK,GAAG,CAAC;AAC3C,CAAC,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ declare const validApiKeys: Record<string, string>;
2
+ export default validApiKeys;
@@ -0,0 +1,6 @@
1
+ const validApiKeys = {
2
+ RLC: process.env.API_KEY_RLC || "",
3
+ TJS: process.env.API_KEY_TJS || ""
4
+ };
5
+ export default validApiKeys;
6
+ //# sourceMappingURL=validApiKeys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validApiKeys.js","sourceRoot":"","sources":["../src/validApiKeys.ts"],"names":[],"mappings":"AAAA,MAAM,YAAY,GAA2B;IAC3C,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE;IAClC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE;CACnC,CAAC;AAEF,eAAe,YAAY,CAAC"}
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@power-rent/phone-validation-adapter",
3
+ "version": "1.0.0",
4
+ "description": "REST API for phone number validation using Twilio Lookup API",
5
+ "main": "./dist/server.js",
6
+ "types": "./dist/server.d.ts",
7
+ "type": "module",
8
+ "files": [
9
+ "dist",
10
+ "LICENSE",
11
+ "README.md"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18.0.0"
15
+ },
16
+ "scripts": {
17
+ "build": "npm run clean && tsc",
18
+ "start": "node dist/server.js",
19
+ "dev": "tsx watch src/server.ts",
20
+ "test": "vitest",
21
+ "test:ui": "vitest --ui",
22
+ "test:run": "vitest run",
23
+ "test:coverage": "vitest run --coverage",
24
+ "lint": "eslint 'src/**/*.{ts,tsx}'",
25
+ "lint:fix": "eslint 'src/**/*.{ts,tsx}' --fix",
26
+ "format": "prettier --write 'src/**/*.{ts,tsx}'",
27
+ "format:check": "prettier --check 'src/**/*.{ts,tsx}'",
28
+ "clean": "rm -rf dist",
29
+ "changeset": "changeset",
30
+ "version": "changeset version",
31
+ "release": "npm run build && changeset publish"
32
+ },
33
+ "keywords": [
34
+ "phone",
35
+ "validation",
36
+ "adapter",
37
+ "twilio",
38
+ "e164",
39
+ "telephone",
40
+ "validator",
41
+ "strategy-pattern"
42
+ ],
43
+ "author": "",
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/power-rent/phone-validation-adapter.git"
48
+ },
49
+ "bugs": {
50
+ "url": "https://github.com/power-rent/phone-validation-adapter/issues"
51
+ },
52
+ "homepage": "https://github.com/power-rent/phone-validation-adapter#readme",
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "devDependencies": {
57
+ "@changesets/cli": "^2.29.8",
58
+ "@types/express": "^5.0.6",
59
+ "@types/node": "^20.0.0",
60
+ "@types/supertest": "^6.0.3",
61
+ "@typescript-eslint/eslint-plugin": "^8.55.0",
62
+ "@typescript-eslint/parser": "^8.55.0",
63
+ "eslint": "^8.0.0",
64
+ "prettier": "^3.0.0",
65
+ "supertest": "^7.2.2",
66
+ "tsx": "^4.0.0",
67
+ "typescript": "^5.0.0",
68
+ "vitest": "^4.0.18"
69
+ },
70
+ "dependencies": {
71
+ "@sentry/node": "^7.120.0",
72
+ "dotenv": "^16.4.5",
73
+ "express": "^5.2.1",
74
+ "twilio": "^3.84.1"
75
+ }
76
+ }