sms-verification-api 0.9.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.
Files changed (50) hide show
  1. package/.env.example +20 -0
  2. package/DEPLOYMENT.md +151 -0
  3. package/README.md +475 -0
  4. package/docs/app/(home)/layout.tsx +7 -0
  5. package/docs/app/(home)/page.tsx +38 -0
  6. package/docs/app/docs/[[...slug]]/page.tsx +59 -0
  7. package/docs/app/docs/layout.tsx +12 -0
  8. package/docs/app/docs-og/[...slug]/route.ts +24 -0
  9. package/docs/app/globals.css +587 -0
  10. package/docs/app/layout.config.tsx +13 -0
  11. package/docs/app/layout.tsx +27 -0
  12. package/docs/app/logo.tsx +35 -0
  13. package/docs/content/docs/API_AUTHENTICATION.md +91 -0
  14. package/docs/content/docs/DEPLOYMENT.md +181 -0
  15. package/docs/content/docs/api/post.mdx +35 -0
  16. package/docs/content/docs/api/verify.mdx +34 -0
  17. package/docs/content/docs/meta.json +8 -0
  18. package/docs/content/docs/verify-legal-name.md +339 -0
  19. package/docs/lib/source.ts +14 -0
  20. package/docs/mdx-components.tsx +12 -0
  21. package/docs/next.config.mjs +51 -0
  22. package/docs/openapi.json +329 -0
  23. package/docs/package.json +37 -0
  24. package/docs/postcss.config.mjs +5 -0
  25. package/docs/scripts/generate-docs.mjs +23 -0
  26. package/docs/source.config.ts +5 -0
  27. package/docs/tsconfig.json +29 -0
  28. package/docs/worker.js +35 -0
  29. package/docs/wrangler.toml +26 -0
  30. package/examples/client.js +119 -0
  31. package/examples/demo.html +325 -0
  32. package/examples/libphonenumber-example.js +120 -0
  33. package/openapi.json +329 -0
  34. package/package.json +71 -0
  35. package/scripts/deploy.sh +63 -0
  36. package/src/identity-verification-server.ts +553 -0
  37. package/src/index.js +8 -0
  38. package/src/sns.js +236 -0
  39. package/src/verify-phone-server.js +448 -0
  40. package/src/verify-phone.ts +551 -0
  41. package/test/api.test.js +201 -0
  42. package/test/integration.test.js +152 -0
  43. package/test/metadata-test.js +73 -0
  44. package/test/server.test.js +143 -0
  45. package/test/setup.js +32 -0
  46. package/test/utils.test.js +186 -0
  47. package/test/verify.test.js +23 -0
  48. package/test/voip.test.js +112 -0
  49. package/vitest.config.js +10 -0
  50. package/wrangler.toml +27 -0
@@ -0,0 +1,186 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ // Import utility functions from the main file
4
+ // We'll need to extract these functions or test them indirectly
5
+ // For now, let's test the phone number validation logic
6
+
7
+ describe('Utility Functions', () => {
8
+ describe('Phone Number Validation', () => {
9
+ const isValidPhoneNumber = (phone) => {
10
+ const phoneRegex = /^\+[1-9]\d{1,14}$/;
11
+ return phoneRegex.test(phone) && phone.length >= 7 && phone.length <= 16;
12
+ };
13
+
14
+ const formatPhoneNumber = (phone) => {
15
+ // Remove all non-digit characters
16
+ const cleaned = phone.replace(/\D/g, '');
17
+
18
+ // Add country code if not present (assuming US)
19
+ if (cleaned.length === 10) {
20
+ return `+1${cleaned}`;
21
+ } else if (cleaned.length === 11 && cleaned.startsWith('1')) {
22
+ return `+${cleaned}`;
23
+ }
24
+
25
+ // Return as is if it already looks like E.164
26
+ return phone.startsWith('+') ? phone : `+${cleaned}`;
27
+ };
28
+
29
+ describe('isValidPhoneNumber', () => {
30
+ it('should validate correct E.164 phone numbers', () => {
31
+ const validNumbers = [
32
+ '+1234567890',
33
+ '+11234567890',
34
+ '+44123456789',
35
+ '+61412345678',
36
+ '+123456789012345'
37
+ ];
38
+
39
+ validNumbers.forEach(number => {
40
+ expect(isValidPhoneNumber(number)).toBe(true);
41
+ });
42
+ });
43
+
44
+ it('should reject invalid phone numbers', () => {
45
+ const invalidNumbers = [
46
+ '1234567890', // Missing +
47
+ '+', // Just plus sign
48
+ '+123', // Too short
49
+ '+12345678901234567890', // Too long
50
+ '+01234567890', // Starts with 0
51
+ 'not-a-number',
52
+ '+abc123def'
53
+ ];
54
+
55
+ invalidNumbers.forEach(number => {
56
+ expect(isValidPhoneNumber(number)).toBe(false);
57
+ });
58
+ });
59
+ });
60
+
61
+ describe('formatPhoneNumber', () => {
62
+ it('should format 10-digit US numbers correctly', () => {
63
+ const testCases = [
64
+ { input: '1234567890', expected: '+11234567890' },
65
+ { input: '(123) 456-7890', expected: '+11234567890' },
66
+ { input: '123-456-7890', expected: '+11234567890' },
67
+ { input: '123.456.7890', expected: '+11234567890' },
68
+ { input: '123 456 7890', expected: '+11234567890' }
69
+ ];
70
+
71
+ testCases.forEach(({ input, expected }) => {
72
+ expect(formatPhoneNumber(input)).toBe(expected);
73
+ });
74
+ });
75
+
76
+ it('should format 11-digit US numbers correctly', () => {
77
+ const testCases = [
78
+ { input: '11234567890', expected: '+11234567890' },
79
+ { input: '1-123-456-7890', expected: '+11234567890' },
80
+ { input: '1 (123) 456-7890', expected: '+11234567890' }
81
+ ];
82
+
83
+ testCases.forEach(({ input, expected }) => {
84
+ expect(formatPhoneNumber(input)).toBe(expected);
85
+ });
86
+ });
87
+
88
+ it('should preserve already formatted E.164 numbers', () => {
89
+ const testCases = [
90
+ '+11234567890', // Already formatted correctly
91
+ '+44123456789',
92
+ '+61412345678'
93
+ ];
94
+
95
+ testCases.forEach(number => {
96
+ expect(formatPhoneNumber(number)).toBe(number);
97
+ });
98
+ });
99
+
100
+ it('should handle edge cases', () => {
101
+ const testCases = [
102
+ { input: '', expected: '+' },
103
+ { input: 'abc', expected: '+' },
104
+ { input: '123abc456', expected: '+123456' }
105
+ ];
106
+
107
+ testCases.forEach(({ input, expected }) => {
108
+ expect(formatPhoneNumber(input)).toBe(expected);
109
+ });
110
+ });
111
+ });
112
+
113
+ describe('Combined Validation and Formatting', () => {
114
+ it('should format and validate phone numbers correctly', () => {
115
+ const testCases = [
116
+ { input: '1234567890', formatted: '+11234567890', valid: true },
117
+ { input: '(123) 456-7890', formatted: '+11234567890', valid: true },
118
+ { input: '123-456-7890', formatted: '+11234567890', valid: true },
119
+ { input: '+11234567890', formatted: '+11234567890', valid: true },
120
+ { input: 'not-a-number', formatted: '+', valid: false },
121
+ { input: '123', formatted: '+123', valid: false },
122
+ { input: '12345678901234567890', formatted: '+12345678901234567890', valid: false }
123
+ ];
124
+
125
+ testCases.forEach(({ input, formatted, valid }) => {
126
+ const formattedResult = formatPhoneNumber(input);
127
+ expect(formattedResult).toBe(formatted);
128
+ expect(isValidPhoneNumber(formattedResult)).toBe(valid);
129
+ });
130
+ });
131
+ });
132
+ });
133
+
134
+ describe('Verification Code Generation', () => {
135
+ const generateVerificationCode = () => {
136
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
137
+ let result = '';
138
+ for (let i = 0; i < 6; i++) {
139
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
140
+ }
141
+ return result;
142
+ };
143
+
144
+ it('should generate 6-character alphanumeric codes', () => {
145
+ for (let i = 0; i < 100; i++) {
146
+ const code = generateVerificationCode();
147
+ expect(code).toMatch(/^[A-Z0-9]{6}$/);
148
+ expect(code.length).toBe(6);
149
+ }
150
+ });
151
+
152
+ it('should generate codes with only uppercase letters and digits', () => {
153
+ for (let i = 0; i < 100; i++) {
154
+ const code = generateVerificationCode();
155
+ expect(code).toMatch(/^[A-Z0-9]+$/);
156
+ expect(code).not.toMatch(/[a-z]/); // No lowercase letters
157
+ }
158
+ });
159
+
160
+ it('should generate different codes on multiple calls', () => {
161
+ const codes = new Set();
162
+ for (let i = 0; i < 100; i++) {
163
+ codes.add(generateVerificationCode());
164
+ }
165
+ // With 100 calls, we should have at least 90 unique codes
166
+ // (allowing for some randomness overlap)
167
+ expect(codes.size).toBeGreaterThan(90);
168
+ });
169
+ });
170
+
171
+ describe('UUID Generation', () => {
172
+ it('should generate valid UUIDs', () => {
173
+ const uuid = crypto.randomUUID();
174
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
175
+ expect(uuid).toMatch(uuidRegex);
176
+ });
177
+
178
+ it('should generate unique UUIDs', () => {
179
+ const uuids = new Set();
180
+ for (let i = 0; i < 100; i++) {
181
+ uuids.add(crypto.randomUUID());
182
+ }
183
+ expect(uuids.size).toBe(100);
184
+ });
185
+ });
186
+ });
@@ -0,0 +1,23 @@
1
+ import verifyPhone from '../src/verify-phone.js';
2
+ import dotenv from 'dotenv';
3
+
4
+ dotenv.config();
5
+
6
+ // Basic usage with custom code
7
+ const result = await verifyPhone({
8
+ phoneNumber: '+1234567890',
9
+ code: 'ABC123',
10
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
11
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
12
+ awsRegion: 'us-east-1',
13
+ blockVoip: true,
14
+ senderId: 'MyApp',
15
+ messageTemplate: 'MyApp: Your code is {code}. Valid for 10 minutes.'
16
+ });
17
+
18
+ if (result.success) {
19
+ console.log('SMS sent successfully:', result.messageId);
20
+ console.log('Code sent:', result.code);
21
+ } else {
22
+ console.error('Failed to send SMS:', result.error);
23
+ }
@@ -0,0 +1,112 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { createApp } from '../src/verify-phone-server.js';
3
+
4
+ describe('VoIP Blocking Functionality', () => {
5
+ let app;
6
+
7
+ beforeEach(() => {
8
+ app = createApp(globalThis.env);
9
+ });
10
+
11
+ describe('blockVoip parameter', () => {
12
+ it('should allow requests when blockVoip is not specified', async () => {
13
+ const req = new Request('http://localhost/api/send-verification?phoneNumber=%2B1234567890', {
14
+ headers: {
15
+ 'X-API-Key': 'test-api-key'
16
+ }
17
+ });
18
+ const res = await app.request(req, globalThis.env);
19
+ const data = await res.json();
20
+
21
+ expect(res.status).toBe(200);
22
+ expect(data.success).toBe(true);
23
+ });
24
+
25
+ it('should allow requests when blockVoip is false', async () => {
26
+ const req = new Request('http://localhost/api/send-verification?phoneNumber=%2B1234567890&blockVoip=false', {
27
+ headers: {
28
+ 'X-API-Key': 'test-api-key'
29
+ }
30
+ });
31
+ const res = await app.request(req, globalThis.env);
32
+ const data = await res.json();
33
+
34
+ expect(res.status).toBe(200);
35
+ expect(data.success).toBe(true);
36
+ });
37
+
38
+ it('should accept blockVoip=true parameter', async () => {
39
+ const req = new Request('http://localhost/api/send-verification?phoneNumber=%2B1234567890&blockVoip=true', {
40
+ headers: {
41
+ 'X-API-Key': 'test-api-key'
42
+ }
43
+ });
44
+ const res = await app.request(req, globalThis.env);
45
+ const data = await res.json();
46
+
47
+ expect(res.status).toBe(200);
48
+ expect(data.success).toBe(true);
49
+ });
50
+ });
51
+
52
+ describe('VoIP detection logic', () => {
53
+ it('should correctly identify VoIP numbers', async () => {
54
+ // Import the function from the module
55
+ const module = await import('../src/verify-phone-server.js');
56
+ const { isPhoneNumberVoip } = module;
57
+
58
+ // Mock the grab function to return VoIP data
59
+ const originalGrab = globalThis.grab;
60
+ globalThis.grab = {
61
+ instance: () => ({
62
+ "phone-lookup": async () => ({
63
+ carrier: {
64
+ name: 'Bandwidth',
65
+ type: 'voip'
66
+ },
67
+ portability: {
68
+ line_type: 'landline'
69
+ }
70
+ })
71
+ })
72
+ };
73
+
74
+ // Test VoIP number
75
+ const isVoip = await isPhoneNumberVoip('+1234567890');
76
+ expect(isVoip).toBe(true);
77
+
78
+ // Mock the grab function to return mobile data
79
+ globalThis.grab = {
80
+ instance: () => ({
81
+ "phone-lookup": async () => ({
82
+ carrier: {
83
+ name: 'Verizon Wireless',
84
+ type: 'mobile'
85
+ },
86
+ portability: {
87
+ line_type: 'mobile'
88
+ }
89
+ })
90
+ })
91
+ };
92
+
93
+ // Test mobile number
94
+ const isMobile = await isPhoneNumberVoip('+1234567890');
95
+ expect(isMobile).toBe(false);
96
+
97
+ // Mock the grab function to return null data
98
+ globalThis.grab = {
99
+ instance: () => ({
100
+ "phone-lookup": async () => null
101
+ })
102
+ };
103
+
104
+ // Test null data
105
+ const isNull = await isPhoneNumberVoip('+1234567890');
106
+ expect(isNull).toBe(false);
107
+
108
+ // Restore original grab function
109
+ globalThis.grab = originalGrab;
110
+ });
111
+ });
112
+ });
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ setupFiles: ['./test/setup.js'],
8
+ mockReset: true,
9
+ },
10
+ });
package/wrangler.toml ADDED
@@ -0,0 +1,27 @@
1
+ name = "sms-verification-api"
2
+ main = "src/index.js"
3
+ compatibility_date = "2024-01-01"
4
+ compatibility_flags = ["nodejs_compat"]
5
+
6
+ # Environment variables
7
+ [vars]
8
+ AWS_REGION = "us-east-1"
9
+ SMS_SENDER_ID = "Verify"
10
+
11
+ # Secrets (these should be set via wrangler secret put)
12
+ TRESTLE_API_KEY = "URx3hLxr6o9uSghxffe2S72oMXuM12Ry9zW80Src"
13
+
14
+ # Staging environment
15
+ [env.staging]
16
+ name = "sms-verification-api-staging"
17
+ vars = { ENVIRONMENT = "staging" }
18
+
19
+ # Production environment
20
+ [env.production]
21
+ name = "sms-verification-api-production"
22
+ vars = { ENVIRONMENT = "production" }
23
+
24
+ # Build configuration
25
+ [build]
26
+ command = "bun run build"
27
+ watch_dir = "src"