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,201 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { createApp } from '../src/verify-phone-server.js';
3
+
4
+ describe('SMS Verification API', () => {
5
+ let app;
6
+
7
+ beforeEach(() => {
8
+ // Create a fresh app instance for each test
9
+ app = createApp(globalThis.env);
10
+ });
11
+
12
+ describe('Health Check', () => {
13
+ it('should return health status', async () => {
14
+ const req = new Request('http://localhost/health');
15
+ const res = await app.request(req, globalThis.env);
16
+ const data = await res.json();
17
+
18
+ expect(res.status).toBe(200);
19
+ expect(data).toMatchObject({
20
+ status: 'ok'
21
+ });
22
+ expect(data.timestamp).toBeDefined();
23
+ });
24
+ });
25
+
26
+ describe('Send Verification Code', () => {
27
+ it('should require API key authentication', async () => {
28
+ const req = new Request('http://localhost/api/send-verification?phoneNumber=%2B1234567890');
29
+ const res = await app.request(req, globalThis.env);
30
+ const data = await res.json();
31
+
32
+ expect(res.status).toBe(401);
33
+ expect(data).toMatchObject({
34
+ error: 'API key required'
35
+ });
36
+ });
37
+
38
+ it('should reject invalid API key', async () => {
39
+ const req = new Request('http://localhost/api/send-verification?phoneNumber=%2B1234567890', {
40
+ headers: {
41
+ 'X-API-Key': 'invalid-key'
42
+ }
43
+ });
44
+ const res = await app.request(req, globalThis.env);
45
+ const data = await res.json();
46
+
47
+ expect(res.status).toBe(401);
48
+ expect(data).toMatchObject({
49
+ error: 'Invalid API key'
50
+ });
51
+ });
52
+
53
+ it('should accept Bearer token authentication', async () => {
54
+ const req = new Request('http://localhost/api/send-verification?phoneNumber=%2B1234567890', {
55
+ headers: {
56
+ 'Authorization': 'Bearer test-api-key'
57
+ }
58
+ });
59
+ const res = await app.request(req, globalThis.env);
60
+ const data = await res.json();
61
+
62
+ expect(res.status).toBe(200);
63
+ expect(data.success).toBe(true);
64
+ });
65
+
66
+ it('should reject invalid phone number format', async () => {
67
+ const req = new Request('http://localhost/api/send-verification?phoneNumber=invalid-phone', {
68
+ headers: {
69
+ 'X-API-Key': 'test-api-key'
70
+ }
71
+ });
72
+ const res = await app.request(req, globalThis.env);
73
+ const data = await res.json();
74
+
75
+ expect(res.status).toBe(400);
76
+ expect(data).toMatchObject({
77
+ error: 'Invalid phone number format'
78
+ });
79
+ });
80
+
81
+ it('should format phone number correctly', async () => {
82
+ const req = new Request('http://localhost/api/send-verification?phoneNumber=1234567890', {
83
+ headers: {
84
+ 'X-API-Key': 'test-api-key'
85
+ }
86
+ });
87
+ const res = await app.request(req, globalThis.env);
88
+ const data = await res.json();
89
+
90
+ expect(res.status).toBe(200);
91
+ expect(data.success).toBe(true);
92
+ });
93
+
94
+ it('should accept blockVoip parameter', async () => {
95
+ const req = new Request('http://localhost/api/send-verification?phoneNumber=%2B1234567890&blockVoip=true', {
96
+ headers: {
97
+ 'X-API-Key': 'test-api-key'
98
+ }
99
+ });
100
+ const res = await app.request(req, globalThis.env);
101
+ const data = await res.json();
102
+
103
+ expect(res.status).toBe(200);
104
+ expect(data.success).toBe(true);
105
+ });
106
+
107
+ it('should handle blockVoip=false parameter', async () => {
108
+ const req = new Request('http://localhost/api/send-verification?phoneNumber=%2B1234567890&blockVoip=false', {
109
+ headers: {
110
+ 'X-API-Key': 'test-api-key'
111
+ }
112
+ });
113
+ const res = await app.request(req, globalThis.env);
114
+ const data = await res.json();
115
+
116
+ expect(res.status).toBe(200);
117
+ expect(data.success).toBe(true);
118
+ });
119
+ });
120
+
121
+ describe('Verify Code', () => {
122
+ it('should require API key authentication', async () => {
123
+ const req = new Request('http://localhost/api/verify-code?phoneNumber=%2B1234567890&code=123456');
124
+ const res = await app.request(req, globalThis.env);
125
+ const data = await res.json();
126
+
127
+ expect(res.status).toBe(401);
128
+ expect(data).toMatchObject({
129
+ error: 'API key required'
130
+ });
131
+ });
132
+
133
+ it('should reject invalid API key', async () => {
134
+ const req = new Request('http://localhost/api/verify-code?phoneNumber=%2B1234567890&code=123456', {
135
+ headers: {
136
+ 'X-API-Key': 'invalid-key'
137
+ }
138
+ });
139
+ const res = await app.request(req, globalThis.env);
140
+ const data = await res.json();
141
+
142
+ expect(res.status).toBe(401);
143
+ expect(data).toMatchObject({
144
+ error: 'Invalid API key'
145
+ });
146
+ });
147
+
148
+ it('should reject invalid phone number format', async () => {
149
+ const req = new Request('http://localhost/api/verify-code?phoneNumber=invalid-phone&code=123456', {
150
+ headers: {
151
+ 'X-API-Key': 'test-api-key'
152
+ }
153
+ });
154
+ const res = await app.request(req, globalThis.env);
155
+ const data = await res.json();
156
+
157
+ expect(res.status).toBe(400);
158
+ expect(data).toMatchObject({
159
+ error: 'Invalid phone number format'
160
+ });
161
+ });
162
+
163
+ it('should accept Bearer token authentication', async () => {
164
+ const req = new Request('http://localhost/api/verify-code?phoneNumber=%2B1234567890&code=123456', {
165
+ headers: {
166
+ 'Authorization': 'Bearer test-api-key'
167
+ }
168
+ });
169
+ const res = await app.request(req, globalThis.env);
170
+ const data = await res.json();
171
+
172
+ expect(res.status).toBe(400); // Will fail because no code was sent, but auth works
173
+ expect(data.error).toBeDefined();
174
+ });
175
+ });
176
+
177
+ describe('Documentation Endpoints', () => {
178
+ it('should serve OpenAPI documentation', async () => {
179
+ const req = new Request('http://localhost/openapi.json');
180
+ const res = await app.request(req, globalThis.env);
181
+ const data = await res.json();
182
+
183
+ expect(res.status).toBe(200);
184
+ expect(data).toMatchObject({
185
+ openapi: '3.0.0',
186
+ info: {
187
+ title: 'SMS API',
188
+ version: '1.0.0'
189
+ }
190
+ });
191
+ });
192
+
193
+ it('should serve Swagger UI', async () => {
194
+ const req = new Request('http://localhost/');
195
+ const res = await app.request(req, globalThis.env);
196
+
197
+ expect(res.status).toBe(200);
198
+ expect(res.headers.get('content-type')).toContain('text/html');
199
+ });
200
+ });
201
+ });
@@ -0,0 +1,152 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { createApp } from '../src/verify-phone-server.js';
3
+
4
+ describe('SMS Verification API Integration Tests', () => {
5
+ let app;
6
+
7
+ beforeEach(() => {
8
+ app = createApp(globalThis.env);
9
+ });
10
+
11
+ describe('Authentication Methods', () => {
12
+ it('should accept both X-API-Key and Bearer token headers', async () => {
13
+ const phoneNumber = '+1234567890';
14
+
15
+ // Test X-API-Key header
16
+ const req1 = new Request(`http://localhost/api/send-verification?phoneNumber=${encodeURIComponent(phoneNumber)}`, {
17
+ headers: {
18
+ 'X-API-Key': 'test-api-key'
19
+ }
20
+ });
21
+ const res1 = await app.request(req1, globalThis.env);
22
+ const data1 = await res1.json();
23
+
24
+ expect(res1.status).toBe(200);
25
+ expect(data1.success).toBe(true);
26
+
27
+ // Test Bearer token header
28
+ const req2 = new Request(`http://localhost/api/send-verification?phoneNumber=${encodeURIComponent(phoneNumber)}`, {
29
+ headers: {
30
+ 'Authorization': 'Bearer test-api-key'
31
+ }
32
+ });
33
+ const res2 = await app.request(req2, globalThis.env);
34
+ const data2 = await res2.json();
35
+
36
+ expect(res2.status).toBe(200);
37
+ expect(data2.success).toBe(true);
38
+ });
39
+
40
+ it('should reject requests without authentication', async () => {
41
+ const phoneNumber = '+1234567890';
42
+
43
+ const req = new Request(`http://localhost/api/send-verification?phoneNumber=${encodeURIComponent(phoneNumber)}`);
44
+ const res = await app.request(req, globalThis.env);
45
+ const data = await res.json();
46
+
47
+ expect(res.status).toBe(401);
48
+ expect(data.error).toBe('API key required');
49
+ });
50
+
51
+ it('should reject requests with invalid authentication', async () => {
52
+ const phoneNumber = '+1234567890';
53
+
54
+ const req = new Request(`http://localhost/api/send-verification?phoneNumber=${encodeURIComponent(phoneNumber)}`, {
55
+ headers: {
56
+ 'X-API-Key': 'invalid-key'
57
+ }
58
+ });
59
+ const res = await app.request(req, globalThis.env);
60
+ const data = await res.json();
61
+
62
+ expect(res.status).toBe(401);
63
+ expect(data.error).toBe('Invalid API key');
64
+ });
65
+ });
66
+
67
+ describe('Phone Number Formatting', () => {
68
+ it('should format various phone number formats correctly', async () => {
69
+ const testCases = [
70
+ '1234567890',
71
+ '11234567890',
72
+ '+1234567890',
73
+ '(123) 456-7890',
74
+ '123-456-7890',
75
+ ];
76
+
77
+ for (const phoneNumber of testCases) {
78
+ const req = new Request(`http://localhost/api/send-verification?phoneNumber=${encodeURIComponent(phoneNumber)}`, {
79
+ headers: {
80
+ 'X-API-Key': 'test-api-key'
81
+ }
82
+ });
83
+ const res = await app.request(req, globalThis.env);
84
+ const data = await res.json();
85
+
86
+ expect(res.status).toBe(200);
87
+ expect(data.success).toBe(true);
88
+ }
89
+ });
90
+
91
+ it('should reject clearly invalid phone numbers', async () => {
92
+ const invalidNumbers = [
93
+ 'not-a-number',
94
+ '123',
95
+ '12345678901234567890', // Too long
96
+ '+', // Just plus sign
97
+ 'abc123def',
98
+ ];
99
+
100
+ for (const invalidNumber of invalidNumbers) {
101
+ const req = new Request(`http://localhost/api/send-verification?phoneNumber=${encodeURIComponent(invalidNumber)}`, {
102
+ headers: {
103
+ 'X-API-Key': 'test-api-key'
104
+ }
105
+ });
106
+ const res = await app.request(req, globalThis.env);
107
+ const data = await res.json();
108
+
109
+ expect(res.status).toBe(400);
110
+ expect(data.error).toBe('Invalid phone number format');
111
+ }
112
+ });
113
+ });
114
+
115
+ describe('API Endpoints', () => {
116
+ it('should handle send verification endpoint', async () => {
117
+ const phoneNumber = '+1234567890';
118
+
119
+ const req = new Request(`http://localhost/api/send-verification?phoneNumber=${encodeURIComponent(phoneNumber)}`, {
120
+ headers: {
121
+ 'X-API-Key': 'test-api-key'
122
+ }
123
+ });
124
+ const res = await app.request(req, globalThis.env);
125
+ const data = await res.json();
126
+
127
+ expect(res.status).toBe(200);
128
+ expect(data).toMatchObject({
129
+ success: true,
130
+ message: 'Verification code sent successfully',
131
+ expiresIn: 600
132
+ });
133
+ expect(data.messageId).toBeDefined();
134
+ });
135
+
136
+ it('should handle verify code endpoint', async () => {
137
+ const phoneNumber = '+1234567890';
138
+ const code = '123456';
139
+
140
+ const req = new Request(`http://localhost/api/verify-code?phoneNumber=${encodeURIComponent(phoneNumber)}&code=${code}`, {
141
+ headers: {
142
+ 'X-API-Key': 'test-api-key'
143
+ }
144
+ });
145
+ const res = await app.request(req, globalThis.env);
146
+ const data = await res.json();
147
+
148
+ expect(res.status).toBe(400); // Will fail because no code was sent, but endpoint works
149
+ expect(data.error).toBeDefined();
150
+ });
151
+ });
152
+ });
@@ -0,0 +1,73 @@
1
+ import { parsePhoneNumber, getNumberType } from 'libphonenumber-js';
2
+
3
+ // Test the metadata functionality
4
+ console.log('=== Testing libphonenumber-js Metadata Functionality ===\n');
5
+
6
+ // Test 1: US toll-free number
7
+ console.log('Test 1: US Toll-free number (+1-800-555-0123)');
8
+ try {
9
+ const phone1 = parsePhoneNumber('+1-800-555-0123');
10
+ console.log(' Parsed:', phone1 ? 'Success' : 'Failed');
11
+ console.log(' Valid:', phone1?.isValid());
12
+ console.log(' Country:', phone1?.country);
13
+ console.log(' National Number:', phone1?.nationalNumber);
14
+ console.log(' Non-geographic:', phone1?.isNonGeographic());
15
+
16
+ // Test getNumberType (requires full metadata)
17
+ try {
18
+ const type1 = getNumberType('+1-800-555-0123');
19
+ console.log(' Number Type:', type1);
20
+ } catch (error) {
21
+ console.log(' Number Type: Error -', error.message);
22
+ }
23
+ } catch (error) {
24
+ console.log(' Parse Error:', error.message);
25
+ }
26
+
27
+ console.log('\n---\n');
28
+
29
+ // Test 2: US mobile number
30
+ console.log('Test 2: US Mobile number (+1-555-123-4567)');
31
+ try {
32
+ const phone2 = parsePhoneNumber('+1-555-123-4567');
33
+ console.log(' Parsed:', phone2 ? 'Success' : 'Failed');
34
+ console.log(' Valid:', phone2?.isValid());
35
+ console.log(' Country:', phone2?.country);
36
+ console.log(' National Number:', phone2?.nationalNumber);
37
+ console.log(' Non-geographic:', phone2?.isNonGeographic());
38
+
39
+ try {
40
+ const type2 = getNumberType('+1-555-123-4567');
41
+ console.log(' Number Type:', type2);
42
+ } catch (error) {
43
+ console.log(' Number Type: Error -', error.message);
44
+ }
45
+ } catch (error) {
46
+ console.log(' Parse Error:', error.message);
47
+ }
48
+
49
+ console.log('\n---\n');
50
+
51
+ // Test 3: UK number
52
+ console.log('Test 3: UK number (+44 20 7946 0958)');
53
+ try {
54
+ const phone3 = parsePhoneNumber('+44 20 7946 0958');
55
+ console.log(' Parsed:', phone3 ? 'Success' : 'Failed');
56
+ console.log(' Valid:', phone3?.isValid());
57
+ console.log(' Country:', phone3?.country);
58
+ console.log(' National Number:', phone3?.nationalNumber);
59
+ console.log(' Non-geographic:', phone3?.isNonGeographic());
60
+
61
+ try {
62
+ const type3 = getNumberType('+44 20 7946 0958');
63
+ console.log(' Number Type:', type3);
64
+ } catch (error) {
65
+ console.log(' Number Type: Error -', error.message);
66
+ }
67
+ } catch (error) {
68
+ console.log(' Parse Error:', error.message);
69
+ }
70
+
71
+ console.log('\n=== Metadata Test Complete ===');
72
+ console.log('\nNote: Number type detection may return undefined with minimal metadata.');
73
+ console.log('Use metadataType: "full" for better phone number type detection.');
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Test file for the SMS Verification API server
3
+ */
4
+
5
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
6
+ import { Hono } from 'hono';
7
+ import app from '../src/verify-phone-server.js';
8
+
9
+ describe('SMS Verification API Server', () => {
10
+ let server;
11
+
12
+ beforeAll(() => {
13
+ server = new Hono();
14
+ server.route('/', app);
15
+ });
16
+
17
+ afterAll(() => {
18
+ // Cleanup if needed
19
+ });
20
+
21
+ describe('Health Check Endpoints', () => {
22
+ it('should return API info on root endpoint', async () => {
23
+ const res = await server.request('/');
24
+ const data = await res.json();
25
+
26
+ expect(res.status).toBe(200);
27
+ expect(data.success).toBe(true);
28
+ expect(data.message).toBe('SMS Verification API');
29
+ expect(data.endpoints).toBeDefined();
30
+ });
31
+
32
+ it('should return health status', async () => {
33
+ const res = await server.request('/health');
34
+ const data = await res.json();
35
+
36
+ expect(res.status).toBe(200);
37
+ expect(data.success).toBe(true);
38
+ expect(data.status).toBe('healthy');
39
+ expect(data.timestamp).toBeDefined();
40
+ });
41
+ });
42
+
43
+ describe('API Authentication', () => {
44
+ it('should require API key for protected endpoints', async () => {
45
+ const res = await server.request('/api/send', {
46
+ method: 'POST',
47
+ headers: {
48
+ 'Content-Type': 'application/json',
49
+ },
50
+ body: JSON.stringify({
51
+ phoneNumber: '+1234567890'
52
+ })
53
+ });
54
+
55
+ expect(res.status).toBe(401);
56
+ });
57
+
58
+ it('should accept valid API key', async () => {
59
+ const res = await server.request('/api/send', {
60
+ method: 'POST',
61
+ headers: {
62
+ 'Content-Type': 'application/json',
63
+ 'X-API-Key': 'sms_1234567890abcdef1234567890abcdef'
64
+ },
65
+ body: JSON.stringify({
66
+ phoneNumber: '+1234567890'
67
+ })
68
+ });
69
+
70
+ // Should not be 401 (unauthorized) - might be 500 due to missing AWS credentials
71
+ expect(res.status).not.toBe(401);
72
+ });
73
+ });
74
+
75
+ describe('Documentation', () => {
76
+ it('should serve OpenAPI documentation', async () => {
77
+ const res = await server.request('/docs');
78
+ expect(res.status).toBe(200);
79
+ });
80
+ });
81
+
82
+ describe('Error Handling', () => {
83
+ it('should return 404 for non-existent endpoints', async () => {
84
+ const res = await server.request('/nonexistent');
85
+ const data = await res.json();
86
+
87
+ expect(res.status).toBe(404);
88
+ expect(data.success).toBe(false);
89
+ expect(data.error).toBe('Not found');
90
+ });
91
+ });
92
+ });
93
+
94
+ // Example usage functions
95
+ export async function sendVerificationCode(phoneNumber, apiKey) {
96
+ const response = await fetch('http://localhost:8787/api/send', {
97
+ method: 'POST',
98
+ headers: {
99
+ 'Content-Type': 'application/json',
100
+ 'X-API-Key': apiKey
101
+ },
102
+ body: JSON.stringify({
103
+ phoneNumber,
104
+ blockVoip: true,
105
+ senderId: 'MyApp'
106
+ })
107
+ });
108
+
109
+ return response.json();
110
+ }
111
+
112
+ export async function verifyCode(phoneNumber, code, apiKey) {
113
+ const response = await fetch('http://localhost:8787/api/verify', {
114
+ method: 'POST',
115
+ headers: {
116
+ 'Content-Type': 'application/json',
117
+ 'X-API-Key': apiKey
118
+ },
119
+ body: JSON.stringify({
120
+ phoneNumber,
121
+ code
122
+ })
123
+ });
124
+
125
+ return response.json();
126
+ }
127
+
128
+ export async function sendGeneralSMS(phoneNumber, message, apiKey) {
129
+ const response = await fetch('http://localhost:8787/api/sms', {
130
+ method: 'POST',
131
+ headers: {
132
+ 'Content-Type': 'application/json',
133
+ 'X-API-Key': apiKey
134
+ },
135
+ body: JSON.stringify({
136
+ phoneNumber,
137
+ message,
138
+ senderId: 'MyApp'
139
+ })
140
+ });
141
+
142
+ return response.json();
143
+ }
package/test/setup.js ADDED
@@ -0,0 +1,32 @@
1
+ // Test setup file
2
+ import { beforeAll, afterAll } from 'vitest';
3
+
4
+ // Set up global environment variables for testing
5
+ globalThis.env = {
6
+ API_KEY: 'test-api-key',
7
+ AWS_ACCESS_KEY_ID: 'test-access-key',
8
+ AWS_SECRET_ACCESS_KEY: 'test-secret-key',
9
+ AWS_REGION: 'us-east-1',
10
+ SMS_SENDER_ID: 'TestVerify',
11
+ };
12
+
13
+ console.log('Test setup - globalThis.env set to:', globalThis.env);
14
+
15
+ // Global test setup
16
+ beforeAll(() => {
17
+ // Ensure environment is set
18
+ if (!globalThis.env) {
19
+ globalThis.env = {
20
+ API_KEY: 'test-api-key',
21
+ AWS_ACCESS_KEY_ID: 'test-access-key',
22
+ AWS_SECRET_ACCESS_KEY: 'test-secret-key',
23
+ AWS_REGION: 'us-east-1',
24
+ SMS_SENDER_ID: 'TestVerify',
25
+ };
26
+ }
27
+ console.log('beforeAll - globalThis.env:', globalThis.env);
28
+ });
29
+
30
+ afterAll(() => {
31
+ // Any global cleanup needed
32
+ });