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,329 @@
1
+ {
2
+ "openapi": "3.0.0",
3
+ "info": {
4
+ "version": "1.0.0",
5
+ "title": "SMS Verification API",
6
+ "description": "A comprehensive SMS verification API using AWS SNS HTTP API with Hono server on Cloudflare Workers. **Authentication Required**: All endpoints except `/health`, `/docs`, and `/openapi.json` require API key authentication.",
7
+ "contact": { "name": "API Support", "email": "support@example.com" },
8
+ "license": { "name": "MIT", "url": "https://opensource.org/licenses/MIT" }
9
+ },
10
+ "servers": [
11
+ {
12
+ "url": "https://sms-verification-api.your-subdomain.workers.dev",
13
+ "description": "Production server"
14
+ },
15
+ {
16
+ "url": "https://sms-verification-api-staging.your-subdomain.workers.dev",
17
+ "description": "Staging server"
18
+ }
19
+ ],
20
+ "components": { "schemas": {}, "parameters": {} },
21
+ "security": [{ "ApiKeyAuth": [] }, { "BearerAuth": [] }],
22
+ "paths": {
23
+ "/health": {
24
+ "get": {
25
+ "tags": ["health"],
26
+ "summary": "Health check",
27
+ "description": "Returns the health status of the API server",
28
+ "responses": {
29
+ "200": {
30
+ "description": "Health check successful",
31
+ "content": {
32
+ "application/json": {
33
+ "schema": {
34
+ "type": "object",
35
+ "properties": {
36
+ "status": { "type": "string", "example": "ok" },
37
+ "timestamp": {
38
+ "type": "string",
39
+ "example": "2024-01-01T00:00:00.000Z"
40
+ },
41
+ "server": {
42
+ "type": "string",
43
+ "example": "Hono on Cloudflare Workers"
44
+ }
45
+ },
46
+ "required": ["status", "timestamp", "server"]
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ },
54
+ "/api/send-verification": {
55
+ "post": {
56
+ "tags": ["verification"],
57
+ "summary": "Send SMS verification code",
58
+ "description": "Sends a 6-digit verification code to the specified phone number via SMS. Optionally accepts a custom code.",
59
+ "security": [{ "ApiKeyAuth": [] }, { "BearerAuth": [] }],
60
+ "requestBody": {
61
+ "content": {
62
+ "application/json": {
63
+ "schema": {
64
+ "type": "object",
65
+ "properties": {
66
+ "phoneNumber": {
67
+ "type": "string",
68
+ "pattern": "^\\+[1-9]\\d{1,14}$",
69
+ "description": "Phone number in E.164 format",
70
+ "example": "+1234567890"
71
+ },
72
+ "code": {
73
+ "type": "string",
74
+ "minLength": 6,
75
+ "maxLength": 6,
76
+ "pattern": "^\\d{6}$",
77
+ "description": "Optional custom 6-digit verification code. If not provided, a random code will be generated.",
78
+ "example": "123456"
79
+ }
80
+ },
81
+ "required": ["phoneNumber"]
82
+ }
83
+ }
84
+ }
85
+ },
86
+ "responses": {
87
+ "200": {
88
+ "description": "Verification code sent successfully",
89
+ "content": {
90
+ "application/json": {
91
+ "schema": {
92
+ "type": "object",
93
+ "properties": {
94
+ "success": { "type": "boolean", "example": true },
95
+ "message": {
96
+ "type": "string",
97
+ "example": "Verification code sent successfully"
98
+ },
99
+ "messageId": {
100
+ "type": "string",
101
+ "example": "aws-message-id-123"
102
+ },
103
+ "expiresIn": {
104
+ "type": "number",
105
+ "description": "Expiration time in seconds",
106
+ "example": 600
107
+ }
108
+ },
109
+ "required": ["success", "message", "messageId", "expiresIn"]
110
+ }
111
+ }
112
+ }
113
+ },
114
+ "400": {
115
+ "description": "Bad request - invalid phone number or other validation error",
116
+ "content": {
117
+ "application/json": {
118
+ "schema": {
119
+ "type": "object",
120
+ "properties": {
121
+ "error": {
122
+ "type": "string",
123
+ "example": "Validation error"
124
+ },
125
+ "details": {
126
+ "type": "string",
127
+ "example": "Additional error details"
128
+ }
129
+ },
130
+ "required": ["error"]
131
+ }
132
+ }
133
+ }
134
+ },
135
+ "429": {
136
+ "description": "Too many requests - rate limited",
137
+ "content": {
138
+ "application/json": {
139
+ "schema": {
140
+ "type": "object",
141
+ "properties": {
142
+ "error": {
143
+ "type": "string",
144
+ "example": "Please wait before requesting another code"
145
+ },
146
+ "retryAfter": {
147
+ "type": "number",
148
+ "description": "Seconds to wait before retry",
149
+ "example": 30
150
+ }
151
+ },
152
+ "required": ["error", "retryAfter"]
153
+ }
154
+ }
155
+ }
156
+ },
157
+ "500": {
158
+ "description": "Internal server error",
159
+ "content": {
160
+ "application/json": {
161
+ "schema": {
162
+ "type": "object",
163
+ "properties": {
164
+ "error": {
165
+ "type": "string",
166
+ "example": "Validation error"
167
+ },
168
+ "details": {
169
+ "type": "string",
170
+ "example": "Additional error details"
171
+ }
172
+ },
173
+ "required": ["error"]
174
+ }
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }
180
+ },
181
+ "/api/verify-code": {
182
+ "post": {
183
+ "tags": ["verification"],
184
+ "summary": "Verify SMS code",
185
+ "description": "Verifies the submitted code against the sent verification code",
186
+ "security": [{ "ApiKeyAuth": [] }, { "BearerAuth": [] }],
187
+ "requestBody": {
188
+ "content": {
189
+ "application/json": {
190
+ "schema": {
191
+ "type": "object",
192
+ "properties": {
193
+ "phoneNumber": {
194
+ "type": "string",
195
+ "pattern": "^\\+[1-9]\\d{1,14}$",
196
+ "description": "Phone number in E.164 format",
197
+ "example": "+1234567890"
198
+ },
199
+ "code": {
200
+ "type": "string",
201
+ "minLength": 6,
202
+ "maxLength": 6,
203
+ "pattern": "^\\d{6}$",
204
+ "description": "6-digit verification code",
205
+ "example": "123456"
206
+ }
207
+ },
208
+ "required": ["phoneNumber", "code"]
209
+ }
210
+ }
211
+ }
212
+ },
213
+ "responses": {
214
+ "200": {
215
+ "description": "Phone number verified successfully",
216
+ "content": {
217
+ "application/json": {
218
+ "schema": {
219
+ "type": "object",
220
+ "properties": {
221
+ "success": { "type": "boolean", "example": true },
222
+ "message": {
223
+ "type": "string",
224
+ "example": "Phone number verified successfully"
225
+ },
226
+ "verificationToken": {
227
+ "type": "string",
228
+ "example": "unique-verification-token"
229
+ },
230
+ "phoneNumber": {
231
+ "type": "string",
232
+ "pattern": "^\\+[1-9]\\d{1,14}$",
233
+ "description": "Phone number in E.164 format",
234
+ "example": "+1234567890"
235
+ }
236
+ },
237
+ "required": [
238
+ "success",
239
+ "message",
240
+ "verificationToken",
241
+ "phoneNumber"
242
+ ]
243
+ }
244
+ }
245
+ }
246
+ },
247
+ "400": {
248
+ "description": "Bad request - invalid code or expired",
249
+ "content": {
250
+ "application/json": {
251
+ "schema": {
252
+ "anyOf": [
253
+ {
254
+ "type": "object",
255
+ "properties": {
256
+ "error": {
257
+ "type": "string",
258
+ "example": "Validation error"
259
+ },
260
+ "details": {
261
+ "type": "string",
262
+ "example": "Additional error details"
263
+ }
264
+ },
265
+ "required": ["error"]
266
+ },
267
+ {
268
+ "type": "object",
269
+ "properties": {
270
+ "error": {
271
+ "type": "string",
272
+ "example": "Invalid verification code"
273
+ },
274
+ "attemptsRemaining": { "type": "number", "example": 3 }
275
+ },
276
+ "required": ["error", "attemptsRemaining"]
277
+ }
278
+ ]
279
+ }
280
+ }
281
+ }
282
+ },
283
+ "429": {
284
+ "description": "Too many verification attempts",
285
+ "content": {
286
+ "application/json": {
287
+ "schema": {
288
+ "type": "object",
289
+ "properties": {
290
+ "error": {
291
+ "type": "string",
292
+ "example": "Validation error"
293
+ },
294
+ "details": {
295
+ "type": "string",
296
+ "example": "Additional error details"
297
+ }
298
+ },
299
+ "required": ["error"]
300
+ }
301
+ }
302
+ }
303
+ },
304
+ "500": {
305
+ "description": "Internal server error",
306
+ "content": {
307
+ "application/json": {
308
+ "schema": {
309
+ "type": "object",
310
+ "properties": {
311
+ "error": {
312
+ "type": "string",
313
+ "example": "Validation error"
314
+ },
315
+ "details": {
316
+ "type": "string",
317
+ "example": "Additional error details"
318
+ }
319
+ },
320
+ "required": ["error"]
321
+ }
322
+ }
323
+ }
324
+ }
325
+ }
326
+ }
327
+ }
328
+ }
329
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "example-openapi",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "build": "next build",
7
+ "build:api": "bun run ./scripts/generate-docs.mjs",
8
+ "dev": "next dev --turbo",
9
+ "start": "next start --port 3001",
10
+ "types:check": "fumadocs-mdx && tsc --noEmit",
11
+ "deploy": "wrangler deploy",
12
+ "deploy:staging": "wrangler deploy --env staging",
13
+ "deploy:production": "wrangler deploy --env production"
14
+ },
15
+ "dependencies": {
16
+ "@fumadocs/mdx-remote": "^1.3.4",
17
+ "fumadocs-core": "15.6.1",
18
+ "fumadocs-mdx": "11.6.10",
19
+ "fumadocs-openapi": "^9.1.0",
20
+ "fumadocs-ui": "15.6.1",
21
+ "lucide-react": "^0.525.0",
22
+ "next": "15.3.4",
23
+ "react": "19.1.0",
24
+ "react-dom": "19.1.0",
25
+ "shiki": "^3.7.0"
26
+ },
27
+ "devDependencies": {
28
+ "@tailwindcss/postcss": "^4.1.11",
29
+ "@types/mdx": "^2.0.13",
30
+ "@types/react": "^19.1.8",
31
+ "@types/react-dom": "^19.1.6",
32
+ "postcss": "^8.5.6",
33
+ "rimraf": "^6.0.1",
34
+ "tailwindcss": "^4.1.11",
35
+ "typescript": "^5.8.3"
36
+ }
37
+ }
@@ -0,0 +1,5 @@
1
+ export default {
2
+ plugins: {
3
+ '@tailwindcss/postcss': {},
4
+ },
5
+ };
@@ -0,0 +1,23 @@
1
+ import * as OpenAPI from 'fumadocs-openapi';
2
+ import { rimraf } from 'rimraf';
3
+
4
+ const out = './content/docs/(api)';
5
+
6
+ async function generate() {
7
+ // clean generated files
8
+ await rimraf(out, {
9
+ filter(v) {
10
+ return !v.endsWith('index.mdx') && !v.endsWith('meta.json');
11
+ },
12
+ });
13
+
14
+ await OpenAPI.generateFiles({
15
+ // input files
16
+ input: ['./openapi.json'],
17
+ output: out,
18
+ includeDescription: false,
19
+
20
+ });
21
+ }
22
+
23
+ void generate();
@@ -0,0 +1,5 @@
1
+ import { defineDocs } from 'fumadocs-mdx/config';
2
+
3
+ export const { docs, meta } = defineDocs({
4
+ dir: 'content/docs',
5
+ });
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "target": "ESNext",
5
+ "lib": ["dom", "dom.iterable", "esnext"],
6
+ "allowJs": true,
7
+ "skipLibCheck": true,
8
+ "strict": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "noEmit": true,
11
+ "esModuleInterop": true,
12
+ "module": "esnext",
13
+ "moduleResolution": "bundler",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "jsx": "preserve",
17
+ "incremental": true,
18
+ "paths": {
19
+ "@/*": ["./*"]
20
+ },
21
+ "plugins": [
22
+ {
23
+ "name": "next"
24
+ }
25
+ ]
26
+ },
27
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
28
+ "exclude": ["node_modules"]
29
+ }
package/docs/worker.js ADDED
@@ -0,0 +1,35 @@
1
+ // Simple worker to serve static Next.js export files
2
+ export default {
3
+ async fetch(request, env, ctx) {
4
+ const url = new URL(request.url);
5
+ let path = url.pathname;
6
+
7
+ // Default to index.html for root
8
+ if (path === '/') {
9
+ path = '/index.html';
10
+ }
11
+
12
+ // Try to serve the file from the out directory
13
+ try {
14
+ const file = await env.ASSETS.fetch(new Request(url.origin + path));
15
+ if (file.status === 200) {
16
+ return file;
17
+ }
18
+ } catch (e) {
19
+ // File not found, continue to fallback
20
+ }
21
+
22
+ // Fallback to index.html for SPA routing
23
+ try {
24
+ const indexFile = await env.ASSETS.fetch(new Request(url.origin + '/index.html'));
25
+ if (indexFile.status === 200) {
26
+ return indexFile;
27
+ }
28
+ } catch (e) {
29
+ // Index file not found
30
+ }
31
+
32
+ // Return 404
33
+ return new Response('Not Found', { status: 404 });
34
+ }
35
+ };
@@ -0,0 +1,26 @@
1
+ name = "sms-verification-docs"
2
+ main = "worker.js"
3
+ compatibility_date = "2024-01-01"
4
+
5
+ # Static assets
6
+ [assets]
7
+ directory = "out"
8
+
9
+ # Build configuration
10
+ [build]
11
+ command = "bun run build"
12
+ watch_dir = "."
13
+
14
+ # Environment variables
15
+ [vars]
16
+ NODE_ENV = "production"
17
+
18
+ # Staging environment
19
+ [env.staging]
20
+ name = "sms-verification-docs-staging"
21
+ vars = { ENVIRONMENT = "staging" }
22
+
23
+ # Production environment
24
+ [env.production]
25
+ name = "sms-verification-docs-production"
26
+ vars = { ENVIRONMENT = "production" }
@@ -0,0 +1,119 @@
1
+ import grab from 'grab-api.js'
2
+ /**
3
+ * Example client for the SMS Verification API
4
+ */
5
+
6
+ const API_BASE_URL = 'http://localhost:8787'; // Change to your deployed URL
7
+ const API_KEY = 'sms_1234567890abcdef1234567890abcdef'; // Change to your API key
8
+
9
+ class SMSVerificationClient {
10
+ constructor(baseUrl = API_BASE_URL, apiKey = API_KEY) {
11
+ this.baseUrl = baseUrl;
12
+ this.apiKey = apiKey;
13
+ }
14
+
15
+ async sendVerificationCode(phoneNumber, options = {}) {
16
+ await grab(`${this.baseUrl}/api/send`, {
17
+ headers: {
18
+ 'X-API-Key': this.apiKey
19
+ },
20
+ phoneNumber,
21
+ ...options
22
+ });
23
+ }
24
+
25
+ async verifyCode(phoneNumber, code) {
26
+ const response = await fetch(`${this.baseUrl}/api/verify`, {
27
+ method: 'POST',
28
+ headers: {
29
+ 'Content-Type': 'application/json',
30
+ 'X-API-Key': this.apiKey
31
+ },
32
+ body: JSON.stringify({
33
+ phoneNumber,
34
+ code
35
+ })
36
+ });
37
+
38
+ return response.json();
39
+ }
40
+
41
+ async sendGeneralSMS(phoneNumber, message, options = {}) {
42
+ const response = await fetch(`${this.baseUrl}/api/sms`, {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ 'X-API-Key': this.apiKey
47
+ },
48
+ body: JSON.stringify({
49
+ phoneNumber,
50
+ message,
51
+ ...options
52
+ })
53
+ });
54
+
55
+ return response.json();
56
+ }
57
+
58
+ async checkHealth() {
59
+ const response = await fetch(`${this.baseUrl}/health`);
60
+ return response.json();
61
+ }
62
+ }
63
+
64
+ // Example usage
65
+ async function example() {
66
+ const client = new SMSVerificationClient();
67
+
68
+ try {
69
+ // Check server health
70
+ console.log('Checking server health...');
71
+ const health = await client.checkHealth();
72
+ console.log('Health:', health);
73
+
74
+ // Send verification code
75
+ console.log('\nSending verification code...');
76
+ const sendResult = await client.sendVerificationCode('+1234567890', {
77
+ blockVoip: true,
78
+ senderId: 'MyApp',
79
+ messageTemplate: 'Your verification code is: {code}. Valid for 10 minutes.'
80
+ });
81
+ console.log('Send result:', sendResult);
82
+
83
+ if (sendResult.success) {
84
+ // Verify the code (in real app, user would enter this)
85
+ console.log('\nVerifying code...');
86
+ const verifyResult = await client.verifyCode('+1234567890', sendResult.code);
87
+ console.log('Verify result:', verifyResult);
88
+ }
89
+
90
+ // Send general SMS
91
+ console.log('\nSending general SMS...');
92
+ const smsResult = await client.sendGeneralSMS(
93
+ '+1234567890',
94
+ 'Hello from your app! This is a test message.',
95
+ { senderId: 'MyApp' }
96
+ );
97
+ console.log('SMS result:', smsResult);
98
+
99
+ } catch (error) {
100
+ console.error('Error:', error);
101
+ }
102
+ }
103
+
104
+ // Node.js usage
105
+ if (typeof module !== 'undefined' && module.exports) {
106
+ module.exports = SMSVerificationClient;
107
+
108
+ // Run example if this file is executed directly
109
+ if (require.main === module) {
110
+ example();
111
+ }
112
+ }
113
+
114
+ // Browser usage
115
+ if (typeof window !== 'undefined') {
116
+ window.SMSVerificationClient = SMSVerificationClient;
117
+ }
118
+
119
+ export default SMSVerificationClient;