@narcisbodea/smstunnel-sdk 1.1.7 → 1.1.8
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/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/server/index.d.mts +36 -42
- package/dist/server/index.d.ts +36 -42
- package/dist/server/index.js +115 -48
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +115 -48
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -3,6 +3,9 @@ interface SmsTunnelConfig {
|
|
|
3
3
|
apiKey: string;
|
|
4
4
|
siteToken: string;
|
|
5
5
|
deviceName: string;
|
|
6
|
+
deviceId: string;
|
|
7
|
+
devicePublicKey: string;
|
|
8
|
+
deviceFingerprint: string;
|
|
6
9
|
}
|
|
7
10
|
interface SmsTunnelStorageAdapter {
|
|
8
11
|
getConfig(): Promise<Partial<SmsTunnelConfig>>;
|
|
@@ -13,6 +16,8 @@ interface SmsTunnelStatus {
|
|
|
13
16
|
paired: boolean;
|
|
14
17
|
serverUrl?: string;
|
|
15
18
|
deviceName?: string;
|
|
19
|
+
e2eEnabled?: boolean;
|
|
20
|
+
deviceFingerprint?: string;
|
|
16
21
|
}
|
|
17
22
|
interface SendSmsResult {
|
|
18
23
|
success: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ interface SmsTunnelConfig {
|
|
|
3
3
|
apiKey: string;
|
|
4
4
|
siteToken: string;
|
|
5
5
|
deviceName: string;
|
|
6
|
+
deviceId: string;
|
|
7
|
+
devicePublicKey: string;
|
|
8
|
+
deviceFingerprint: string;
|
|
6
9
|
}
|
|
7
10
|
interface SmsTunnelStorageAdapter {
|
|
8
11
|
getConfig(): Promise<Partial<SmsTunnelConfig>>;
|
|
@@ -13,6 +16,8 @@ interface SmsTunnelStatus {
|
|
|
13
16
|
paired: boolean;
|
|
14
17
|
serverUrl?: string;
|
|
15
18
|
deviceName?: string;
|
|
19
|
+
e2eEnabled?: boolean;
|
|
20
|
+
deviceFingerprint?: string;
|
|
16
21
|
}
|
|
17
22
|
interface SendSmsResult {
|
|
18
23
|
success: boolean;
|
package/dist/server/index.d.mts
CHANGED
|
@@ -5,6 +5,9 @@ interface SmsTunnelConfig {
|
|
|
5
5
|
apiKey: string;
|
|
6
6
|
siteToken: string;
|
|
7
7
|
deviceName: string;
|
|
8
|
+
deviceId: string;
|
|
9
|
+
devicePublicKey: string;
|
|
10
|
+
deviceFingerprint: string;
|
|
8
11
|
}
|
|
9
12
|
interface SmsTunnelStorageAdapter {
|
|
10
13
|
getConfig(): Promise<Partial<SmsTunnelConfig>>;
|
|
@@ -34,6 +37,8 @@ interface SmsTunnelStatus {
|
|
|
34
37
|
paired: boolean;
|
|
35
38
|
serverUrl?: string;
|
|
36
39
|
deviceName?: string;
|
|
40
|
+
e2eEnabled?: boolean;
|
|
41
|
+
deviceFingerprint?: string;
|
|
37
42
|
}
|
|
38
43
|
interface SendSmsResult {
|
|
39
44
|
success: boolean;
|
|
@@ -84,6 +89,17 @@ interface LegacyCallbackBody {
|
|
|
84
89
|
device_name?: string;
|
|
85
90
|
status?: string;
|
|
86
91
|
}
|
|
92
|
+
interface PairWithApiKeyResult {
|
|
93
|
+
success: boolean;
|
|
94
|
+
deviceName?: string;
|
|
95
|
+
error?: string;
|
|
96
|
+
}
|
|
97
|
+
interface ExchangeKeysResult {
|
|
98
|
+
success: boolean;
|
|
99
|
+
fingerprint?: string;
|
|
100
|
+
deviceName?: string;
|
|
101
|
+
error?: string;
|
|
102
|
+
}
|
|
87
103
|
interface DeviceE2EStatus {
|
|
88
104
|
encryptionEnabled: boolean;
|
|
89
105
|
hasPublicKey: boolean;
|
|
@@ -141,6 +157,11 @@ declare class SmsTunnelService {
|
|
|
141
157
|
* Handle legacy WordPress-compatible callback.
|
|
142
158
|
*/
|
|
143
159
|
handleLegacyCallback(siteToken: string, apiKey: string, deviceName?: string): Promise<boolean>;
|
|
160
|
+
/**
|
|
161
|
+
* Pair using an API key directly (alternative to QR).
|
|
162
|
+
* Validates the key by calling /api/v1/devices, then saves it.
|
|
163
|
+
*/
|
|
164
|
+
pairWithApiKey(apiKey: string): Promise<PairWithApiKeyResult>;
|
|
144
165
|
/**
|
|
145
166
|
* Send SMS via SMSTunnel API.
|
|
146
167
|
*/
|
|
@@ -185,25 +206,11 @@ declare class SmsTunnelService {
|
|
|
185
206
|
*/
|
|
186
207
|
getUsage(): Promise<any>;
|
|
187
208
|
/**
|
|
188
|
-
*
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
success: boolean;
|
|
192
|
-
data?: DeviceE2EStatus;
|
|
193
|
-
error?: string;
|
|
194
|
-
}>;
|
|
195
|
-
/**
|
|
196
|
-
* Get device public key for E2E encryption.
|
|
197
|
-
*/
|
|
198
|
-
getDevicePublicKey(deviceId: string): Promise<{
|
|
199
|
-
success: boolean;
|
|
200
|
-
data?: DevicePublicKeyInfo;
|
|
201
|
-
error?: string;
|
|
202
|
-
}>;
|
|
203
|
-
/**
|
|
204
|
-
* Verify that a stored fingerprint matches the current device key.
|
|
209
|
+
* Exchange encryption keys with the paired device.
|
|
210
|
+
* Fetches the device's public key from SMSTunnel server and stores it locally.
|
|
211
|
+
* Called automatically after pairing, or manually via POST /smstunnel/exchange-keys.
|
|
205
212
|
*/
|
|
206
|
-
|
|
213
|
+
exchangeKeys(): Promise<ExchangeKeysResult>;
|
|
207
214
|
/**
|
|
208
215
|
* Send encrypted SMS via SMSTunnel API.
|
|
209
216
|
* The payload is encrypted client-side with the device's RSA public key.
|
|
@@ -255,6 +262,13 @@ declare class SmsTunnelController {
|
|
|
255
262
|
setupCallbackWp(body: LegacyCallbackBody): Promise<{
|
|
256
263
|
success: boolean;
|
|
257
264
|
}>;
|
|
265
|
+
/**
|
|
266
|
+
* Pair using an API key directly (alternative to QR).
|
|
267
|
+
* Route: POST /{prefix}/pair-with-key
|
|
268
|
+
*/
|
|
269
|
+
pairWithKey(body: {
|
|
270
|
+
apiKey: string;
|
|
271
|
+
}): Promise<PairWithApiKeyResult>;
|
|
258
272
|
/**
|
|
259
273
|
* Unpair the connected device.
|
|
260
274
|
* Route: POST /{prefix}/unpair
|
|
@@ -313,31 +327,11 @@ declare class SmsTunnelController {
|
|
|
313
327
|
*/
|
|
314
328
|
receivedSms(): Promise<any>;
|
|
315
329
|
/**
|
|
316
|
-
*
|
|
317
|
-
*
|
|
330
|
+
* Exchange encryption keys with the paired device.
|
|
331
|
+
* Fetches the device's public key from SMSTunnel and stores it locally.
|
|
332
|
+
* Route: POST /{prefix}/exchange-keys
|
|
318
333
|
*/
|
|
319
|
-
|
|
320
|
-
success: boolean;
|
|
321
|
-
data?: DeviceE2EStatus;
|
|
322
|
-
error?: string;
|
|
323
|
-
}>;
|
|
324
|
-
/**
|
|
325
|
-
* Get device public key for E2E encryption.
|
|
326
|
-
* Route: GET /{prefix}/public-key/:deviceId
|
|
327
|
-
*/
|
|
328
|
-
publicKey(deviceId: string): Promise<{
|
|
329
|
-
success: boolean;
|
|
330
|
-
data?: DevicePublicKeyInfo;
|
|
331
|
-
error?: string;
|
|
332
|
-
}>;
|
|
333
|
-
/**
|
|
334
|
-
* Verify device key fingerprint.
|
|
335
|
-
* Route: POST /{prefix}/verify-key
|
|
336
|
-
*/
|
|
337
|
-
verifyKey(body: {
|
|
338
|
-
deviceId: string;
|
|
339
|
-
fingerprint: string;
|
|
340
|
-
}): Promise<VerifyDeviceKeyResult>;
|
|
334
|
+
exchangeKeys(): Promise<ExchangeKeysResult>;
|
|
341
335
|
/**
|
|
342
336
|
* Send encrypted SMS.
|
|
343
337
|
* Route: POST /{prefix}/send-encrypted
|
package/dist/server/index.d.ts
CHANGED
|
@@ -5,6 +5,9 @@ interface SmsTunnelConfig {
|
|
|
5
5
|
apiKey: string;
|
|
6
6
|
siteToken: string;
|
|
7
7
|
deviceName: string;
|
|
8
|
+
deviceId: string;
|
|
9
|
+
devicePublicKey: string;
|
|
10
|
+
deviceFingerprint: string;
|
|
8
11
|
}
|
|
9
12
|
interface SmsTunnelStorageAdapter {
|
|
10
13
|
getConfig(): Promise<Partial<SmsTunnelConfig>>;
|
|
@@ -34,6 +37,8 @@ interface SmsTunnelStatus {
|
|
|
34
37
|
paired: boolean;
|
|
35
38
|
serverUrl?: string;
|
|
36
39
|
deviceName?: string;
|
|
40
|
+
e2eEnabled?: boolean;
|
|
41
|
+
deviceFingerprint?: string;
|
|
37
42
|
}
|
|
38
43
|
interface SendSmsResult {
|
|
39
44
|
success: boolean;
|
|
@@ -84,6 +89,17 @@ interface LegacyCallbackBody {
|
|
|
84
89
|
device_name?: string;
|
|
85
90
|
status?: string;
|
|
86
91
|
}
|
|
92
|
+
interface PairWithApiKeyResult {
|
|
93
|
+
success: boolean;
|
|
94
|
+
deviceName?: string;
|
|
95
|
+
error?: string;
|
|
96
|
+
}
|
|
97
|
+
interface ExchangeKeysResult {
|
|
98
|
+
success: boolean;
|
|
99
|
+
fingerprint?: string;
|
|
100
|
+
deviceName?: string;
|
|
101
|
+
error?: string;
|
|
102
|
+
}
|
|
87
103
|
interface DeviceE2EStatus {
|
|
88
104
|
encryptionEnabled: boolean;
|
|
89
105
|
hasPublicKey: boolean;
|
|
@@ -141,6 +157,11 @@ declare class SmsTunnelService {
|
|
|
141
157
|
* Handle legacy WordPress-compatible callback.
|
|
142
158
|
*/
|
|
143
159
|
handleLegacyCallback(siteToken: string, apiKey: string, deviceName?: string): Promise<boolean>;
|
|
160
|
+
/**
|
|
161
|
+
* Pair using an API key directly (alternative to QR).
|
|
162
|
+
* Validates the key by calling /api/v1/devices, then saves it.
|
|
163
|
+
*/
|
|
164
|
+
pairWithApiKey(apiKey: string): Promise<PairWithApiKeyResult>;
|
|
144
165
|
/**
|
|
145
166
|
* Send SMS via SMSTunnel API.
|
|
146
167
|
*/
|
|
@@ -185,25 +206,11 @@ declare class SmsTunnelService {
|
|
|
185
206
|
*/
|
|
186
207
|
getUsage(): Promise<any>;
|
|
187
208
|
/**
|
|
188
|
-
*
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
success: boolean;
|
|
192
|
-
data?: DeviceE2EStatus;
|
|
193
|
-
error?: string;
|
|
194
|
-
}>;
|
|
195
|
-
/**
|
|
196
|
-
* Get device public key for E2E encryption.
|
|
197
|
-
*/
|
|
198
|
-
getDevicePublicKey(deviceId: string): Promise<{
|
|
199
|
-
success: boolean;
|
|
200
|
-
data?: DevicePublicKeyInfo;
|
|
201
|
-
error?: string;
|
|
202
|
-
}>;
|
|
203
|
-
/**
|
|
204
|
-
* Verify that a stored fingerprint matches the current device key.
|
|
209
|
+
* Exchange encryption keys with the paired device.
|
|
210
|
+
* Fetches the device's public key from SMSTunnel server and stores it locally.
|
|
211
|
+
* Called automatically after pairing, or manually via POST /smstunnel/exchange-keys.
|
|
205
212
|
*/
|
|
206
|
-
|
|
213
|
+
exchangeKeys(): Promise<ExchangeKeysResult>;
|
|
207
214
|
/**
|
|
208
215
|
* Send encrypted SMS via SMSTunnel API.
|
|
209
216
|
* The payload is encrypted client-side with the device's RSA public key.
|
|
@@ -255,6 +262,13 @@ declare class SmsTunnelController {
|
|
|
255
262
|
setupCallbackWp(body: LegacyCallbackBody): Promise<{
|
|
256
263
|
success: boolean;
|
|
257
264
|
}>;
|
|
265
|
+
/**
|
|
266
|
+
* Pair using an API key directly (alternative to QR).
|
|
267
|
+
* Route: POST /{prefix}/pair-with-key
|
|
268
|
+
*/
|
|
269
|
+
pairWithKey(body: {
|
|
270
|
+
apiKey: string;
|
|
271
|
+
}): Promise<PairWithApiKeyResult>;
|
|
258
272
|
/**
|
|
259
273
|
* Unpair the connected device.
|
|
260
274
|
* Route: POST /{prefix}/unpair
|
|
@@ -313,31 +327,11 @@ declare class SmsTunnelController {
|
|
|
313
327
|
*/
|
|
314
328
|
receivedSms(): Promise<any>;
|
|
315
329
|
/**
|
|
316
|
-
*
|
|
317
|
-
*
|
|
330
|
+
* Exchange encryption keys with the paired device.
|
|
331
|
+
* Fetches the device's public key from SMSTunnel and stores it locally.
|
|
332
|
+
* Route: POST /{prefix}/exchange-keys
|
|
318
333
|
*/
|
|
319
|
-
|
|
320
|
-
success: boolean;
|
|
321
|
-
data?: DeviceE2EStatus;
|
|
322
|
-
error?: string;
|
|
323
|
-
}>;
|
|
324
|
-
/**
|
|
325
|
-
* Get device public key for E2E encryption.
|
|
326
|
-
* Route: GET /{prefix}/public-key/:deviceId
|
|
327
|
-
*/
|
|
328
|
-
publicKey(deviceId: string): Promise<{
|
|
329
|
-
success: boolean;
|
|
330
|
-
data?: DevicePublicKeyInfo;
|
|
331
|
-
error?: string;
|
|
332
|
-
}>;
|
|
333
|
-
/**
|
|
334
|
-
* Verify device key fingerprint.
|
|
335
|
-
* Route: POST /{prefix}/verify-key
|
|
336
|
-
*/
|
|
337
|
-
verifyKey(body: {
|
|
338
|
-
deviceId: string;
|
|
339
|
-
fingerprint: string;
|
|
340
|
-
}): Promise<VerifyDeviceKeyResult>;
|
|
334
|
+
exchangeKeys(): Promise<ExchangeKeysResult>;
|
|
341
335
|
/**
|
|
342
336
|
* Send encrypted SMS.
|
|
343
337
|
* Route: POST /{prefix}/send-encrypted
|
package/dist/server/index.js
CHANGED
|
@@ -75,7 +75,9 @@ var SmsTunnelService = class {
|
|
|
75
75
|
return {
|
|
76
76
|
paired: !!config.apiKey,
|
|
77
77
|
serverUrl: config.serverUrl,
|
|
78
|
-
deviceName: config.deviceName
|
|
78
|
+
deviceName: config.deviceName,
|
|
79
|
+
e2eEnabled: !!config.devicePublicKey,
|
|
80
|
+
deviceFingerprint: config.deviceFingerprint
|
|
79
81
|
};
|
|
80
82
|
}
|
|
81
83
|
/**
|
|
@@ -214,13 +216,22 @@ var SmsTunnelService = class {
|
|
|
214
216
|
);
|
|
215
217
|
return false;
|
|
216
218
|
}
|
|
217
|
-
|
|
219
|
+
const update = {
|
|
218
220
|
apiKey: body.apiKey,
|
|
219
221
|
deviceName: body.deviceName || "Android Phone"
|
|
220
|
-
}
|
|
222
|
+
};
|
|
223
|
+
if (body.deviceId) {
|
|
224
|
+
update.deviceId = body.deviceId;
|
|
225
|
+
}
|
|
226
|
+
await this.options.storage.updateConfig(update);
|
|
221
227
|
this.logger.log(
|
|
222
228
|
`SMSTunnel paired! Device: ${body.deviceName}, API key: ${body.apiKey?.substring(0, 12)}...`
|
|
223
229
|
);
|
|
230
|
+
if (body.deviceId) {
|
|
231
|
+
this.exchangeKeys().catch(
|
|
232
|
+
(err) => this.logger.warn(`Auto key exchange failed: ${err.message}`)
|
|
233
|
+
);
|
|
234
|
+
}
|
|
224
235
|
return true;
|
|
225
236
|
}
|
|
226
237
|
/**
|
|
@@ -238,6 +249,46 @@ var SmsTunnelService = class {
|
|
|
238
249
|
this.logger.log(`SMSTunnel paired (legacy). Device: ${deviceName || "unknown"}`);
|
|
239
250
|
return true;
|
|
240
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Pair using an API key directly (alternative to QR).
|
|
254
|
+
* Validates the key by calling /api/v1/devices, then saves it.
|
|
255
|
+
*/
|
|
256
|
+
async pairWithApiKey(apiKey) {
|
|
257
|
+
const config = await this.options.storage.getConfig();
|
|
258
|
+
if (!config.serverUrl) {
|
|
259
|
+
return { success: false, error: "SMSTunnel server URL is not configured." };
|
|
260
|
+
}
|
|
261
|
+
const serverUrl = config.serverUrl.replace(/\/$/, "");
|
|
262
|
+
try {
|
|
263
|
+
const res = await fetch(`${serverUrl}/api/v1/devices`, {
|
|
264
|
+
headers: { "X-API-Key": apiKey }
|
|
265
|
+
});
|
|
266
|
+
if (!res.ok) {
|
|
267
|
+
if (res.status === 401 || res.status === 403) {
|
|
268
|
+
return { success: false, error: "API key invalid or expired." };
|
|
269
|
+
}
|
|
270
|
+
return { success: false, error: `SMSTunnel returned error: ${res.status}` };
|
|
271
|
+
}
|
|
272
|
+
const data = await res.json();
|
|
273
|
+
const devices = data?.devices || [];
|
|
274
|
+
const device = devices[0];
|
|
275
|
+
const deviceName = device?.name || "Android Phone";
|
|
276
|
+
const deviceId = device?.id || device?._id;
|
|
277
|
+
const update = { apiKey, deviceName };
|
|
278
|
+
if (deviceId) update.deviceId = deviceId;
|
|
279
|
+
await this.options.storage.updateConfig(update);
|
|
280
|
+
this.logger.log(`Paired via API key. Device: ${deviceName}`);
|
|
281
|
+
if (deviceId) {
|
|
282
|
+
this.exchangeKeys().catch(
|
|
283
|
+
(err) => this.logger.warn(`Auto key exchange failed: ${err.message}`)
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
return { success: true, deviceName };
|
|
287
|
+
} catch (err) {
|
|
288
|
+
this.logger.error(`Pair with API key error: ${err.message}`);
|
|
289
|
+
return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
241
292
|
/**
|
|
242
293
|
* Send SMS via SMSTunnel API.
|
|
243
294
|
*/
|
|
@@ -334,35 +385,59 @@ var SmsTunnelService = class {
|
|
|
334
385
|
}
|
|
335
386
|
// ─── E2E Encryption ──────────────────────────────
|
|
336
387
|
/**
|
|
337
|
-
*
|
|
388
|
+
* Exchange encryption keys with the paired device.
|
|
389
|
+
* Fetches the device's public key from SMSTunnel server and stores it locally.
|
|
390
|
+
* Called automatically after pairing, or manually via POST /smstunnel/exchange-keys.
|
|
338
391
|
*/
|
|
339
|
-
async
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
* Get device public key for E2E encryption.
|
|
344
|
-
*/
|
|
345
|
-
async getDevicePublicKey(deviceId) {
|
|
346
|
-
return this.apiGet(`/api/v1/devices/${deviceId}/public-key`);
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* Verify that a stored fingerprint matches the current device key.
|
|
350
|
-
*/
|
|
351
|
-
async verifyDeviceKey(deviceId, storedFingerprint) {
|
|
352
|
-
const res = await this.getDeviceE2EStatus(deviceId);
|
|
353
|
-
if (!res.success || !res.data) {
|
|
354
|
-
return { valid: false, needsRePairing: true };
|
|
392
|
+
async exchangeKeys() {
|
|
393
|
+
const config = await this.options.storage.getConfig();
|
|
394
|
+
if (!config.apiKey || !config.serverUrl) {
|
|
395
|
+
return { success: false, error: "SMSTunnel is not configured." };
|
|
355
396
|
}
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
397
|
+
const serverUrl = config.serverUrl.replace(/\/$/, "");
|
|
398
|
+
try {
|
|
399
|
+
let deviceId = config.deviceId;
|
|
400
|
+
if (!deviceId) {
|
|
401
|
+
const listRes = await fetch(`${serverUrl}/api/v1/devices`, {
|
|
402
|
+
headers: { "X-API-Key": config.apiKey }
|
|
403
|
+
});
|
|
404
|
+
if (!listRes.ok) {
|
|
405
|
+
return { success: false, error: `Could not list devices: ${listRes.status}` };
|
|
406
|
+
}
|
|
407
|
+
const listData = await listRes.json();
|
|
408
|
+
const devices = listData?.devices || [];
|
|
409
|
+
if (devices.length === 0) {
|
|
410
|
+
return { success: false, error: "No paired device found." };
|
|
411
|
+
}
|
|
412
|
+
deviceId = devices[0].id || devices[0]._id;
|
|
413
|
+
await this.options.storage.updateConfig({ deviceId });
|
|
414
|
+
}
|
|
415
|
+
const res = await fetch(`${serverUrl}/api/v1/devices/${deviceId}`, {
|
|
416
|
+
headers: { "X-API-Key": config.apiKey }
|
|
417
|
+
});
|
|
418
|
+
if (!res.ok) {
|
|
419
|
+
return { success: false, error: `Could not fetch device: ${res.status}` };
|
|
420
|
+
}
|
|
421
|
+
const device = await res.json();
|
|
422
|
+
if (!device.publicKey) {
|
|
423
|
+
return { success: false, error: "Device has no public key. Enable encryption in SMSTunnel app on the phone." };
|
|
424
|
+
}
|
|
425
|
+
await this.options.storage.updateConfig({
|
|
426
|
+
deviceId,
|
|
427
|
+
devicePublicKey: device.publicKey,
|
|
428
|
+
deviceFingerprint: device.publicKeyFingerprint,
|
|
429
|
+
deviceName: device.name || config.deviceName
|
|
430
|
+
});
|
|
431
|
+
this.logger.log(`E2E keys exchanged. Fingerprint: ${device.publicKeyFingerprint}`);
|
|
432
|
+
return {
|
|
433
|
+
success: true,
|
|
434
|
+
fingerprint: device.publicKeyFingerprint,
|
|
435
|
+
deviceName: device.name
|
|
436
|
+
};
|
|
437
|
+
} catch (err) {
|
|
438
|
+
this.logger.error(`Key exchange error: ${err.message}`);
|
|
439
|
+
return { success: false, error: `Key exchange failed: ${err.message}` };
|
|
359
440
|
}
|
|
360
|
-
const matches = status.publicKeyFingerprint === storedFingerprint;
|
|
361
|
-
return {
|
|
362
|
-
valid: matches,
|
|
363
|
-
currentFingerprint: status.publicKeyFingerprint,
|
|
364
|
-
needsRePairing: !matches
|
|
365
|
-
};
|
|
366
441
|
}
|
|
367
442
|
/**
|
|
368
443
|
* Send encrypted SMS via SMSTunnel API.
|
|
@@ -466,6 +541,9 @@ var SmsTunnelController = class {
|
|
|
466
541
|
);
|
|
467
542
|
return { success };
|
|
468
543
|
}
|
|
544
|
+
async pairWithKey(body) {
|
|
545
|
+
return this.smsTunnelService.pairWithApiKey(body.apiKey);
|
|
546
|
+
}
|
|
469
547
|
async unpair() {
|
|
470
548
|
await this.smsTunnelService.unpair();
|
|
471
549
|
return { success: true };
|
|
@@ -489,14 +567,8 @@ var SmsTunnelController = class {
|
|
|
489
567
|
async receivedSms() {
|
|
490
568
|
return this.smsTunnelService.getReceivedSms();
|
|
491
569
|
}
|
|
492
|
-
async
|
|
493
|
-
return this.smsTunnelService.
|
|
494
|
-
}
|
|
495
|
-
async publicKey(deviceId) {
|
|
496
|
-
return this.smsTunnelService.getDevicePublicKey(deviceId);
|
|
497
|
-
}
|
|
498
|
-
async verifyKey(body) {
|
|
499
|
-
return this.smsTunnelService.verifyDeviceKey(body.deviceId, body.fingerprint);
|
|
570
|
+
async exchangeKeys() {
|
|
571
|
+
return this.smsTunnelService.exchangeKeys();
|
|
500
572
|
}
|
|
501
573
|
async sendEncrypted(body) {
|
|
502
574
|
return this.smsTunnelService.sendEncryptedSms(
|
|
@@ -536,6 +608,10 @@ __decorateClass([
|
|
|
536
608
|
(0, import_common2.Post)("wp-json/smstunnel/v1/setup-callback"),
|
|
537
609
|
__decorateParam(0, (0, import_common2.Body)())
|
|
538
610
|
], SmsTunnelController.prototype, "setupCallbackWp", 1);
|
|
611
|
+
__decorateClass([
|
|
612
|
+
(0, import_common2.Post)("smstunnel/pair-with-key"),
|
|
613
|
+
__decorateParam(0, (0, import_common2.Body)())
|
|
614
|
+
], SmsTunnelController.prototype, "pairWithKey", 1);
|
|
539
615
|
__decorateClass([
|
|
540
616
|
(0, import_common2.Post)("smstunnel/unpair")
|
|
541
617
|
], SmsTunnelController.prototype, "unpair", 1);
|
|
@@ -563,17 +639,8 @@ __decorateClass([
|
|
|
563
639
|
(0, import_common2.Get)("smstunnel/received")
|
|
564
640
|
], SmsTunnelController.prototype, "receivedSms", 1);
|
|
565
641
|
__decorateClass([
|
|
566
|
-
(0, import_common2.
|
|
567
|
-
|
|
568
|
-
], SmsTunnelController.prototype, "e2eStatus", 1);
|
|
569
|
-
__decorateClass([
|
|
570
|
-
(0, import_common2.Get)("smstunnel/public-key/:deviceId"),
|
|
571
|
-
__decorateParam(0, (0, import_common2.Param)("deviceId"))
|
|
572
|
-
], SmsTunnelController.prototype, "publicKey", 1);
|
|
573
|
-
__decorateClass([
|
|
574
|
-
(0, import_common2.Post)("smstunnel/verify-key"),
|
|
575
|
-
__decorateParam(0, (0, import_common2.Body)())
|
|
576
|
-
], SmsTunnelController.prototype, "verifyKey", 1);
|
|
642
|
+
(0, import_common2.Post)("smstunnel/exchange-keys")
|
|
643
|
+
], SmsTunnelController.prototype, "exchangeKeys", 1);
|
|
577
644
|
__decorateClass([
|
|
578
645
|
(0, import_common2.Post)("smstunnel/send-encrypted"),
|
|
579
646
|
__decorateParam(0, (0, import_common2.Body)())
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/index.ts","../../src/server/smstunnel.module.ts","../../src/server/smstunnel.service.ts","../../src/server/smstunnel.constants.ts","../../src/server/smstunnel.controller.ts"],"sourcesContent":["export { SmsTunnelModule } from './smstunnel.module';\r\nexport { SmsTunnelService } from './smstunnel.service';\r\nexport { SmsTunnelController } from './smstunnel.controller';\r\nexport {\r\n SMSTUNNEL_OPTIONS,\r\n SMSTUNNEL_PUBLIC_PATHS,\r\n SMSTUNNEL_DEFAULT_PREFIX,\r\n getSmsTunnelPublicPaths,\r\n} from './smstunnel.constants';\r\nexport type {\r\n SmsTunnelConfig,\r\n SmsTunnelStorageAdapter,\r\n SmsTunnelModuleOptions,\r\n SmsTunnelModuleAsyncOptions,\r\n SmsTunnelStatus,\r\n SendSmsResult,\r\n CreateTokenResult,\r\n PairingStatusResult,\r\n PairingCallbackBody,\r\n LegacyCallbackBody,\r\n SaasActivateResult,\r\n DeviceE2EStatus,\r\n DevicePublicKeyInfo,\r\n VerifyDeviceKeyResult,\r\n} from './smstunnel.interfaces';\r\n","import { DynamicModule, Module, Provider } from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SmsTunnelController } from './smstunnel.controller';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelModuleAsyncOptions,\r\n} from './smstunnel.interfaces';\r\n\r\n@Module({})\r\nexport class SmsTunnelModule {\r\n static forRoot(options: SmsTunnelModuleOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useValue: options,\r\n };\r\n\r\n const controllers = [SmsTunnelController];\r\n if (!options.enableLegacyCallback) {\r\n // Legacy callback is always registered but won't match\r\n // if the consumer doesn't enable it - no-op\r\n }\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers,\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n\r\n static forRootAsync(asyncOptions: SmsTunnelModuleAsyncOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useFactory: asyncOptions.useFactory,\r\n inject: asyncOptions.inject || [],\r\n };\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers: [SmsTunnelController],\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n}\r\n","import { Inject, Injectable, Logger } from '@nestjs/common';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelStatus,\r\n SendSmsResult,\r\n CreateTokenResult,\r\n PairingStatusResult,\r\n PairingCallbackBody,\r\n SaasActivateResult,\r\n DeviceE2EStatus,\r\n DevicePublicKeyInfo,\r\n VerifyDeviceKeyResult,\r\n} from './smstunnel.interfaces';\r\n\r\n@Injectable()\r\nexport class SmsTunnelService {\r\n private readonly logger = new Logger(SmsTunnelService.name);\r\n\r\n constructor(\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {}\r\n\r\n /**\r\n * Get current pairing status.\r\n */\r\n async getStatus(): Promise<SmsTunnelStatus> {\r\n const config = await this.options.storage.getConfig();\r\n return {\r\n paired: !!config.apiKey,\r\n serverUrl: config.serverUrl,\r\n deviceName: config.deviceName,\r\n };\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel server.\r\n * Uses Enterprise/SaaS flow when enterpriseApiKey is configured,\r\n * otherwise falls back to public-create flow.\r\n */\r\n async createPairingToken(): Promise<CreateTokenResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel server URL is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n if (this.options.enterpriseApiKey) {\r\n return this.createPairingTokenEnterprise(serverUrl);\r\n }\r\n\r\n return this.createPairingTokenPublic(serverUrl);\r\n }\r\n\r\n /**\r\n * Public flow: POST /api/v1/pairing/public-create\r\n */\r\n private async createPairingTokenPublic(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/public-create`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n source: 'api',\r\n displayName: this.options.displayName || 'App',\r\n displayUrl: this.options.displayUrl || this.options.callbackBaseUrl,\r\n context: { callbackUrl },\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel create token failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: any = await res.json();\r\n\r\n await this.options.storage.updateConfig({ siteToken: data.token });\r\n\r\n this.logger.log(`Pairing token created: ${data.token?.substring(0, 12)}...`);\r\n\r\n return {\r\n success: true,\r\n token: data.token,\r\n qrData: data.qrData,\r\n expiresAt: data.expiresAt,\r\n pollUrl: data.pollUrl || `${serverUrl}/api/v1/pairing/${data.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel create token error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Enterprise/SaaS flow: POST /api/v1/saas/activate\r\n * Creates sub-accounts under the Enterprise account.\r\n */\r\n private async createPairingTokenEnterprise(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/saas/activate`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': this.options.enterpriseApiKey!,\r\n },\r\n body: JSON.stringify({\r\n clientName: this.options.displayName || 'App',\r\n externalId: this.options.externalId || 'default',\r\n callbackUrl,\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel SaaS activate failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: SaasActivateResult = await res.json();\r\n\r\n if (!data.success || !data.activation) {\r\n return { success: false, error: data.error || 'SaaS activation failed' };\r\n }\r\n\r\n // Save the activation token as siteToken\r\n const configUpdate: Record<string, any> = { siteToken: data.activation.token };\r\n\r\n // If the server returned an API key, save it too\r\n if (data.apiKey?.key) {\r\n configUpdate.apiKey = data.apiKey.key;\r\n }\r\n\r\n await this.options.storage.updateConfig(configUpdate);\r\n\r\n this.logger.log(\r\n `SaaS pairing token created: ${data.activation.token?.substring(0, 12)}... (isNew: ${data.isNew})`,\r\n );\r\n\r\n return {\r\n success: true,\r\n token: data.activation.token,\r\n qrData: data.activation.qrData,\r\n expiresAt: data.activation.expiresAt,\r\n pollUrl: `${serverUrl}/api/v1/pairing/${data.activation.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel SaaS activate error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Check pairing status by polling SMSTunnel.\r\n */\r\n async getPairingStatus(token: string): Promise<PairingStatusResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { status: 'error', error: 'SMSTunnel server URL not configured' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/${token}`);\r\n if (!res.ok) {\r\n return { status: 'expired' };\r\n }\r\n return (await res.json()) as PairingStatusResult;\r\n } catch {\r\n return { status: 'error', error: 'Could not contact SMSTunnel' };\r\n }\r\n }\r\n\r\n /**\r\n * Handle callback from SMSTunnel after pairing completes.\r\n */\r\n async handlePairingCallback(body: PairingCallbackBody): Promise<boolean> {\r\n if (body.event !== 'pairing_completed') {\r\n this.logger.warn(`Unknown callback event: ${body.event}`);\r\n return false;\r\n }\r\n\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken && config.siteToken !== body.token) {\r\n this.logger.warn(\r\n `Callback token mismatch: expected ${config.siteToken?.substring(0, 8)}, got ${body.token?.substring(0, 8)}`,\r\n );\r\n return false;\r\n }\r\n\r\n await this.options.storage.updateConfig({\r\n apiKey: body.apiKey,\r\n deviceName: body.deviceName || 'Android Phone',\r\n });\r\n\r\n this.logger.log(\r\n `SMSTunnel paired! Device: ${body.deviceName}, API key: ${body.apiKey?.substring(0, 12)}...`,\r\n );\r\n return true;\r\n }\r\n\r\n /**\r\n * Handle legacy WordPress-compatible callback.\r\n */\r\n async handleLegacyCallback(\r\n siteToken: string,\r\n apiKey: string,\r\n deviceName?: string,\r\n ): Promise<boolean> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken !== siteToken) {\r\n this.logger.warn('Legacy callback: site_token mismatch');\r\n return false;\r\n }\r\n\r\n const update: Partial<{ apiKey: string; deviceName: string }> = { apiKey };\r\n if (deviceName) update.deviceName = deviceName;\r\n\r\n await this.options.storage.updateConfig(update);\r\n this.logger.log(`SMSTunnel paired (legacy). Device: ${deviceName || 'unknown'}`);\r\n return true;\r\n }\r\n\r\n /**\r\n * Send SMS via SMSTunnel API.\r\n */\r\n async sendSms(to: string, message: string): Promise<SendSmsResult> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (!config.apiKey) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel is not configured. Pair a device first.',\r\n };\r\n }\r\n\r\n if (!config.serverUrl) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel server URL is not configured.',\r\n };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/sms/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify({ to, message }),\r\n });\r\n\r\n const data: any = await res.json();\r\n\r\n if (data.success) {\r\n this.logger.log(`SMS sent via SMSTunnel: ${data.messageId} -> ${to}`);\r\n return { success: true, messageId: data.messageId };\r\n }\r\n\r\n return {\r\n success: false,\r\n error: data.error || data.message || 'SMSTunnel returned an error',\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMS send error: ${err.message}`);\r\n return { success: false, error: `Could not send SMS: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Unpair - clear saved API key and device info.\r\n */\r\n async unpair(): Promise<void> {\r\n await this.options.storage.clearConfig();\r\n this.logger.log('SMSTunnel unpaired');\r\n }\r\n\r\n /**\r\n * Update server URL in config.\r\n */\r\n async updateServerUrl(serverUrl: string): Promise<void> {\r\n await this.options.storage.updateConfig({\r\n serverUrl: serverUrl.replace(/\\/$/, ''),\r\n });\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send 2FA SMS via SMSTunnel API.\r\n */\r\n async send2fa(to: string, code: string, template?: string): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send-2fa', { to, code, template });\r\n }\r\n\r\n /**\r\n * Send bulk SMS via SMSTunnel API.\r\n */\r\n async sendBulk(\r\n messages: Array<{ to: string; message: string }>,\r\n ): Promise<{ success: boolean; results?: any[]; error?: string }> {\r\n return this.apiPost('/api/v1/sms/send-bulk', { messages });\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n */\r\n async getSmsStatus(messageId: string): Promise<any> {\r\n return this.apiGet(`/api/v1/sms/status/${messageId}`);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n */\r\n async getReceivedSms(): Promise<any> {\r\n return this.apiGet('/api/v1/sms/received');\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n */\r\n async getDevices(): Promise<any> {\r\n return this.apiGet('/api/v1/devices');\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n */\r\n async getUsage(): Promise<any> {\r\n return this.apiGet('/api/v1/account/usage');\r\n }\r\n\r\n // ─── E2E Encryption ──────────────────────────────\r\n\r\n /**\r\n * Get E2E encryption status for a device.\r\n */\r\n async getDeviceE2EStatus(deviceId: string): Promise<{ success: boolean; data?: DeviceE2EStatus; error?: string }> {\r\n return this.apiGet(`/api/v1/devices/${deviceId}/e2e-status`);\r\n }\r\n\r\n /**\r\n * Get device public key for E2E encryption.\r\n */\r\n async getDevicePublicKey(deviceId: string): Promise<{ success: boolean; data?: DevicePublicKeyInfo; error?: string }> {\r\n return this.apiGet(`/api/v1/devices/${deviceId}/public-key`);\r\n }\r\n\r\n /**\r\n * Verify that a stored fingerprint matches the current device key.\r\n */\r\n async verifyDeviceKey(deviceId: string, storedFingerprint: string): Promise<VerifyDeviceKeyResult> {\r\n const res = await this.getDeviceE2EStatus(deviceId);\r\n if (!res.success || !res.data) {\r\n return { valid: false, needsRePairing: true };\r\n }\r\n const status = res.data;\r\n if (!status.hasPublicKey || !status.publicKeyFingerprint) {\r\n return { valid: false, needsRePairing: true };\r\n }\r\n const matches = status.publicKeyFingerprint === storedFingerprint;\r\n return {\r\n valid: matches,\r\n currentFingerprint: status.publicKeyFingerprint,\r\n needsRePairing: !matches,\r\n };\r\n }\r\n\r\n /**\r\n * Send encrypted SMS via SMSTunnel API.\r\n * The payload is encrypted client-side with the device's RSA public key.\r\n */\r\n async sendEncryptedSms(\r\n encryptedPayload: string,\r\n deviceId: string,\r\n is2FA: boolean = false,\r\n ): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send', {\r\n encrypted: true,\r\n encryptedPayload,\r\n deviceId,\r\n is2FA,\r\n });\r\n }\r\n\r\n /**\r\n * List pairings for the authenticated user.\r\n */\r\n async getPairings(): Promise<any> {\r\n return this.apiGet('/api/v1/pairings');\r\n }\r\n\r\n // ─── Internal helpers ──────────────────────────────\r\n\r\n /** Make an authenticated GET request to SMSTunnel */\r\n private async apiGet(path: string): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n\r\n /** Make an authenticated POST request to SMSTunnel */\r\n private async apiPost(path: string, body: any): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify(body),\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n}\r\n","export const SMSTUNNEL_OPTIONS = 'SMSTUNNEL_OPTIONS';\r\n\r\nexport const SMSTUNNEL_DEFAULT_PREFIX = 'smstunnel';\r\n\r\n/**\r\n * Public paths that should be excluded from auth guards.\r\n * Use with your global guard to skip auth on callback/polling routes.\r\n *\r\n * Example (NestJS):\r\n * if (SMSTUNNEL_PUBLIC_PATHS.some(p => request.url.includes(p))) return true;\r\n */\r\nexport const SMSTUNNEL_PUBLIC_PATHS = [\r\n '/smstunnel/pairing-status/',\r\n '/smstunnel/callback',\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n];\r\n\r\n/**\r\n * Returns public paths with a custom prefix.\r\n */\r\nexport function getSmsTunnelPublicPaths(prefix: string): string[] {\r\n return [\r\n `/${prefix}/pairing-status/`,\r\n `/${prefix}/callback`,\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n ];\r\n}\r\n","import {\r\n Controller,\r\n Post,\r\n Get,\r\n Body,\r\n Param,\r\n Inject,\r\n Logger,\r\n} from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n PairingCallbackBody,\r\n LegacyCallbackBody,\r\n} from './smstunnel.interfaces';\r\n\r\n/**\r\n * Decorator to mark routes as public (no auth).\r\n * If you have your own @Public() decorator, the module re-exports\r\n * SMSTUNNEL_PUBLIC_PATHS so you can exclude them in your guard.\r\n */\r\nfunction SmsTunnelPublic() {\r\n // We use Reflect metadata so consumers can detect public routes\r\n return (target: any, key: string, descriptor: PropertyDescriptor) => {\r\n Reflect.defineMetadata('isPublic', true, descriptor.value);\r\n return descriptor;\r\n };\r\n}\r\n\r\n@Controller()\r\nexport class SmsTunnelController {\r\n private readonly logger = new Logger(SmsTunnelController.name);\r\n private readonly prefix: string;\r\n\r\n constructor(\r\n private readonly smsTunnelService: SmsTunnelService,\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {\r\n this.prefix = 'smstunnel';\r\n }\r\n\r\n /**\r\n * Get current pairing status.\r\n * Route: GET /{prefix}/status\r\n */\r\n @Get('smstunnel/status')\r\n async getStatus() {\r\n return this.smsTunnelService.getStatus();\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel.\r\n * Route: POST /{prefix}/create-token\r\n */\r\n @Post('smstunnel/create-token')\r\n async createToken() {\r\n return this.smsTunnelService.createPairingToken();\r\n }\r\n\r\n /**\r\n * Proxy polling to SMSTunnel (avoids CORS issues).\r\n * This should be public (no auth) - just proxying SMSTunnel's endpoint.\r\n * Route: GET /{prefix}/pairing-status/:token\r\n */\r\n @SmsTunnelPublic()\r\n @Get('smstunnel/pairing-status/:token')\r\n async pairingStatus(@Param('token') token: string) {\r\n return this.smsTunnelService.getPairingStatus(token);\r\n }\r\n\r\n /**\r\n * Callback from SMSTunnel after pairing completes.\r\n * Route: POST /{prefix}/callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('smstunnel/callback')\r\n async pairingCallback(@Body() body: PairingCallbackBody) {\r\n this.logger.log(\r\n `SMSTunnel callback: event=${body.event}, token=${body.token?.substring(0, 8)}...`,\r\n );\r\n const success = await this.smsTunnelService.handlePairingCallback(body);\r\n return { success };\r\n }\r\n\r\n /**\r\n * WordPress-compatible legacy callback.\r\n * Route: POST /wp-json/smstunnel/v1/setup-callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('wp-json/smstunnel/v1/setup-callback')\r\n async setupCallbackWp(@Body() body: LegacyCallbackBody) {\r\n this.logger.log(`SMSTunnel legacy callback. Status: ${body.status || 'unknown'}`);\r\n const success = await this.smsTunnelService.handleLegacyCallback(\r\n body.site_token,\r\n body.api_key,\r\n body.device_name,\r\n );\r\n return { success };\r\n }\r\n\r\n /**\r\n * Unpair the connected device.\r\n * Route: POST /{prefix}/unpair\r\n */\r\n @Post('smstunnel/unpair')\r\n async unpair() {\r\n await this.smsTunnelService.unpair();\r\n return { success: true };\r\n }\r\n\r\n /**\r\n * Send an SMS via SMSTunnel.\r\n * Route: POST /{prefix}/send\r\n */\r\n @Post('smstunnel/send')\r\n async sendSms(@Body() body: { to: string; message: string }) {\r\n return this.smsTunnelService.sendSms(body.to, body.message);\r\n }\r\n\r\n /**\r\n * Update server URL configuration.\r\n * Route: POST /{prefix}/update-config\r\n */\r\n @Post('smstunnel/update-config')\r\n async updateConfig(@Body() body: { serverUrl: string }) {\r\n await this.smsTunnelService.updateServerUrl(body.serverUrl);\r\n return { success: true };\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send a 2FA SMS.\r\n * Route: POST /{prefix}/send-2fa\r\n */\r\n @Post('smstunnel/send-2fa')\r\n async send2fa(@Body() body: { to: string; code: string; template?: string }) {\r\n return this.smsTunnelService.send2fa(body.to, body.code, body.template);\r\n }\r\n\r\n /**\r\n * Send bulk SMS.\r\n * Route: POST /{prefix}/send-bulk\r\n */\r\n @Post('smstunnel/send-bulk')\r\n async sendBulk(@Body() body: { messages: Array<{ to: string; message: string }> }) {\r\n return this.smsTunnelService.sendBulk(body.messages);\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n * Route: GET /{prefix}/sms-status/:messageId\r\n */\r\n @Get('smstunnel/sms-status/:messageId')\r\n async smsStatus(@Param('messageId') messageId: string) {\r\n return this.smsTunnelService.getSmsStatus(messageId);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n * Route: GET /{prefix}/received\r\n */\r\n @Get('smstunnel/received')\r\n async receivedSms() {\r\n return this.smsTunnelService.getReceivedSms();\r\n }\r\n\r\n // ─── E2E Encryption ────────────────────────────────\r\n\r\n /**\r\n * Get E2E encryption status for a device.\r\n * Route: GET /{prefix}/e2e-status/:deviceId\r\n */\r\n @Get('smstunnel/e2e-status/:deviceId')\r\n async e2eStatus(@Param('deviceId') deviceId: string) {\r\n return this.smsTunnelService.getDeviceE2EStatus(deviceId);\r\n }\r\n\r\n /**\r\n * Get device public key for E2E encryption.\r\n * Route: GET /{prefix}/public-key/:deviceId\r\n */\r\n @Get('smstunnel/public-key/:deviceId')\r\n async publicKey(@Param('deviceId') deviceId: string) {\r\n return this.smsTunnelService.getDevicePublicKey(deviceId);\r\n }\r\n\r\n /**\r\n * Verify device key fingerprint.\r\n * Route: POST /{prefix}/verify-key\r\n */\r\n @Post('smstunnel/verify-key')\r\n async verifyKey(@Body() body: { deviceId: string; fingerprint: string }) {\r\n return this.smsTunnelService.verifyDeviceKey(body.deviceId, body.fingerprint);\r\n }\r\n\r\n /**\r\n * Send encrypted SMS.\r\n * Route: POST /{prefix}/send-encrypted\r\n */\r\n @Post('smstunnel/send-encrypted')\r\n async sendEncrypted(\r\n @Body() body: { encryptedPayload: string; deviceId: string; is2FA?: boolean },\r\n ) {\r\n return this.smsTunnelService.sendEncryptedSms(\r\n body.encryptedPayload,\r\n body.deviceId,\r\n body.is2FA,\r\n );\r\n }\r\n\r\n /**\r\n * List pairings.\r\n * Route: GET /{prefix}/pairings\r\n */\r\n @Get('smstunnel/pairings')\r\n async pairings() {\r\n return this.smsTunnelService.getPairings();\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n * Route: GET /{prefix}/devices\r\n */\r\n @Get('smstunnel/devices')\r\n async devices() {\r\n return this.smsTunnelService.getDevices();\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n * Route: GET /{prefix}/usage\r\n */\r\n @Get('smstunnel/usage')\r\n async usage() {\r\n return this.smsTunnelService.getUsage();\r\n }\r\n}\r\n"],"mappings":"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","names":["import_common","import_common"]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/index.ts","../../src/server/smstunnel.module.ts","../../src/server/smstunnel.service.ts","../../src/server/smstunnel.constants.ts","../../src/server/smstunnel.controller.ts"],"sourcesContent":["export { SmsTunnelModule } from './smstunnel.module';\r\nexport { SmsTunnelService } from './smstunnel.service';\r\nexport { SmsTunnelController } from './smstunnel.controller';\r\nexport {\r\n SMSTUNNEL_OPTIONS,\r\n SMSTUNNEL_PUBLIC_PATHS,\r\n SMSTUNNEL_DEFAULT_PREFIX,\r\n getSmsTunnelPublicPaths,\r\n} from './smstunnel.constants';\r\nexport type {\r\n SmsTunnelConfig,\r\n SmsTunnelStorageAdapter,\r\n SmsTunnelModuleOptions,\r\n SmsTunnelModuleAsyncOptions,\r\n SmsTunnelStatus,\r\n SendSmsResult,\r\n CreateTokenResult,\r\n PairingStatusResult,\r\n PairingCallbackBody,\r\n LegacyCallbackBody,\r\n SaasActivateResult,\r\n DeviceE2EStatus,\r\n DevicePublicKeyInfo,\r\n VerifyDeviceKeyResult,\r\n} from './smstunnel.interfaces';\r\n","import { DynamicModule, Module, Provider } from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SmsTunnelController } from './smstunnel.controller';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelModuleAsyncOptions,\r\n} from './smstunnel.interfaces';\r\n\r\n@Module({})\r\nexport class SmsTunnelModule {\r\n static forRoot(options: SmsTunnelModuleOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useValue: options,\r\n };\r\n\r\n const controllers = [SmsTunnelController];\r\n if (!options.enableLegacyCallback) {\r\n // Legacy callback is always registered but won't match\r\n // if the consumer doesn't enable it - no-op\r\n }\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers,\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n\r\n static forRootAsync(asyncOptions: SmsTunnelModuleAsyncOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useFactory: asyncOptions.useFactory,\r\n inject: asyncOptions.inject || [],\r\n };\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers: [SmsTunnelController],\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n}\r\n","import { Inject, Injectable, Logger } from '@nestjs/common';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelStatus,\r\n SendSmsResult,\r\n CreateTokenResult,\r\n PairingStatusResult,\r\n PairingCallbackBody,\r\n PairWithApiKeyResult,\r\n ExchangeKeysResult,\r\n SaasActivateResult,\r\n} from './smstunnel.interfaces';\r\n\r\n@Injectable()\r\nexport class SmsTunnelService {\r\n private readonly logger = new Logger(SmsTunnelService.name);\r\n\r\n constructor(\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {}\r\n\r\n /**\r\n * Get current pairing status.\r\n */\r\n async getStatus(): Promise<SmsTunnelStatus> {\r\n const config = await this.options.storage.getConfig();\r\n return {\r\n paired: !!config.apiKey,\r\n serverUrl: config.serverUrl,\r\n deviceName: config.deviceName,\r\n e2eEnabled: !!config.devicePublicKey,\r\n deviceFingerprint: config.deviceFingerprint,\r\n };\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel server.\r\n * Uses Enterprise/SaaS flow when enterpriseApiKey is configured,\r\n * otherwise falls back to public-create flow.\r\n */\r\n async createPairingToken(): Promise<CreateTokenResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel server URL is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n if (this.options.enterpriseApiKey) {\r\n return this.createPairingTokenEnterprise(serverUrl);\r\n }\r\n\r\n return this.createPairingTokenPublic(serverUrl);\r\n }\r\n\r\n /**\r\n * Public flow: POST /api/v1/pairing/public-create\r\n */\r\n private async createPairingTokenPublic(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/public-create`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n source: 'api',\r\n displayName: this.options.displayName || 'App',\r\n displayUrl: this.options.displayUrl || this.options.callbackBaseUrl,\r\n context: { callbackUrl },\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel create token failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: any = await res.json();\r\n\r\n await this.options.storage.updateConfig({ siteToken: data.token });\r\n\r\n this.logger.log(`Pairing token created: ${data.token?.substring(0, 12)}...`);\r\n\r\n return {\r\n success: true,\r\n token: data.token,\r\n qrData: data.qrData,\r\n expiresAt: data.expiresAt,\r\n pollUrl: data.pollUrl || `${serverUrl}/api/v1/pairing/${data.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel create token error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Enterprise/SaaS flow: POST /api/v1/saas/activate\r\n * Creates sub-accounts under the Enterprise account.\r\n */\r\n private async createPairingTokenEnterprise(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/saas/activate`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': this.options.enterpriseApiKey!,\r\n },\r\n body: JSON.stringify({\r\n clientName: this.options.displayName || 'App',\r\n externalId: this.options.externalId || 'default',\r\n callbackUrl,\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel SaaS activate failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: SaasActivateResult = await res.json();\r\n\r\n if (!data.success || !data.activation) {\r\n return { success: false, error: data.error || 'SaaS activation failed' };\r\n }\r\n\r\n // Save the activation token as siteToken\r\n const configUpdate: Record<string, any> = { siteToken: data.activation.token };\r\n\r\n // If the server returned an API key, save it too\r\n if (data.apiKey?.key) {\r\n configUpdate.apiKey = data.apiKey.key;\r\n }\r\n\r\n await this.options.storage.updateConfig(configUpdate);\r\n\r\n this.logger.log(\r\n `SaaS pairing token created: ${data.activation.token?.substring(0, 12)}... (isNew: ${data.isNew})`,\r\n );\r\n\r\n return {\r\n success: true,\r\n token: data.activation.token,\r\n qrData: data.activation.qrData,\r\n expiresAt: data.activation.expiresAt,\r\n pollUrl: `${serverUrl}/api/v1/pairing/${data.activation.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel SaaS activate error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Check pairing status by polling SMSTunnel.\r\n */\r\n async getPairingStatus(token: string): Promise<PairingStatusResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { status: 'error', error: 'SMSTunnel server URL not configured' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/${token}`);\r\n if (!res.ok) {\r\n return { status: 'expired' };\r\n }\r\n return (await res.json()) as PairingStatusResult;\r\n } catch {\r\n return { status: 'error', error: 'Could not contact SMSTunnel' };\r\n }\r\n }\r\n\r\n /**\r\n * Handle callback from SMSTunnel after pairing completes.\r\n */\r\n async handlePairingCallback(body: PairingCallbackBody): Promise<boolean> {\r\n if (body.event !== 'pairing_completed') {\r\n this.logger.warn(`Unknown callback event: ${body.event}`);\r\n return false;\r\n }\r\n\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken && config.siteToken !== body.token) {\r\n this.logger.warn(\r\n `Callback token mismatch: expected ${config.siteToken?.substring(0, 8)}, got ${body.token?.substring(0, 8)}`,\r\n );\r\n return false;\r\n }\r\n\r\n const update: Record<string, any> = {\r\n apiKey: body.apiKey,\r\n deviceName: body.deviceName || 'Android Phone',\r\n };\r\n if (body.deviceId) {\r\n update.deviceId = body.deviceId;\r\n }\r\n await this.options.storage.updateConfig(update);\r\n\r\n this.logger.log(\r\n `SMSTunnel paired! Device: ${body.deviceName}, API key: ${body.apiKey?.substring(0, 12)}...`,\r\n );\r\n\r\n // Auto-exchange keys after pairing\r\n if (body.deviceId) {\r\n this.exchangeKeys().catch((err) =>\r\n this.logger.warn(`Auto key exchange failed: ${err.message}`),\r\n );\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Handle legacy WordPress-compatible callback.\r\n */\r\n async handleLegacyCallback(\r\n siteToken: string,\r\n apiKey: string,\r\n deviceName?: string,\r\n ): Promise<boolean> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken !== siteToken) {\r\n this.logger.warn('Legacy callback: site_token mismatch');\r\n return false;\r\n }\r\n\r\n const update: Partial<{ apiKey: string; deviceName: string }> = { apiKey };\r\n if (deviceName) update.deviceName = deviceName;\r\n\r\n await this.options.storage.updateConfig(update);\r\n this.logger.log(`SMSTunnel paired (legacy). Device: ${deviceName || 'unknown'}`);\r\n return true;\r\n }\r\n\r\n /**\r\n * Pair using an API key directly (alternative to QR).\r\n * Validates the key by calling /api/v1/devices, then saves it.\r\n */\r\n async pairWithApiKey(apiKey: string): Promise<PairWithApiKeyResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel server URL is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/devices`, {\r\n headers: { 'X-API-Key': apiKey },\r\n });\r\n\r\n if (!res.ok) {\r\n if (res.status === 401 || res.status === 403) {\r\n return { success: false, error: 'API key invalid or expired.' };\r\n }\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: any = await res.json();\r\n const devices = data?.devices || [];\r\n const device = devices[0];\r\n const deviceName = device?.name || 'Android Phone';\r\n const deviceId = device?.id || device?._id;\r\n\r\n const update: Record<string, any> = { apiKey, deviceName };\r\n if (deviceId) update.deviceId = deviceId;\r\n await this.options.storage.updateConfig(update);\r\n\r\n this.logger.log(`Paired via API key. Device: ${deviceName}`);\r\n\r\n // Auto-exchange keys\r\n if (deviceId) {\r\n this.exchangeKeys().catch((err) =>\r\n this.logger.warn(`Auto key exchange failed: ${err.message}`),\r\n );\r\n }\r\n\r\n return { success: true, deviceName };\r\n } catch (err: any) {\r\n this.logger.error(`Pair with API key error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Send SMS via SMSTunnel API.\r\n */\r\n async sendSms(to: string, message: string): Promise<SendSmsResult> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (!config.apiKey) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel is not configured. Pair a device first.',\r\n };\r\n }\r\n\r\n if (!config.serverUrl) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel server URL is not configured.',\r\n };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/sms/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify({ to, message }),\r\n });\r\n\r\n const data: any = await res.json();\r\n\r\n if (data.success) {\r\n this.logger.log(`SMS sent via SMSTunnel: ${data.messageId} -> ${to}`);\r\n return { success: true, messageId: data.messageId };\r\n }\r\n\r\n return {\r\n success: false,\r\n error: data.error || data.message || 'SMSTunnel returned an error',\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMS send error: ${err.message}`);\r\n return { success: false, error: `Could not send SMS: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Unpair - clear saved API key and device info.\r\n */\r\n async unpair(): Promise<void> {\r\n await this.options.storage.clearConfig();\r\n this.logger.log('SMSTunnel unpaired');\r\n }\r\n\r\n /**\r\n * Update server URL in config.\r\n */\r\n async updateServerUrl(serverUrl: string): Promise<void> {\r\n await this.options.storage.updateConfig({\r\n serverUrl: serverUrl.replace(/\\/$/, ''),\r\n });\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send 2FA SMS via SMSTunnel API.\r\n */\r\n async send2fa(to: string, code: string, template?: string): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send-2fa', { to, code, template });\r\n }\r\n\r\n /**\r\n * Send bulk SMS via SMSTunnel API.\r\n */\r\n async sendBulk(\r\n messages: Array<{ to: string; message: string }>,\r\n ): Promise<{ success: boolean; results?: any[]; error?: string }> {\r\n return this.apiPost('/api/v1/sms/send-bulk', { messages });\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n */\r\n async getSmsStatus(messageId: string): Promise<any> {\r\n return this.apiGet(`/api/v1/sms/status/${messageId}`);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n */\r\n async getReceivedSms(): Promise<any> {\r\n return this.apiGet('/api/v1/sms/received');\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n */\r\n async getDevices(): Promise<any> {\r\n return this.apiGet('/api/v1/devices');\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n */\r\n async getUsage(): Promise<any> {\r\n return this.apiGet('/api/v1/account/usage');\r\n }\r\n\r\n // ─── E2E Encryption ──────────────────────────────\r\n\r\n /**\r\n * Exchange encryption keys with the paired device.\r\n * Fetches the device's public key from SMSTunnel server and stores it locally.\r\n * Called automatically after pairing, or manually via POST /smstunnel/exchange-keys.\r\n */\r\n async exchangeKeys(): Promise<ExchangeKeysResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n // Resolve deviceId if we don't have it\r\n let deviceId = config.deviceId;\r\n if (!deviceId) {\r\n const listRes = await fetch(`${serverUrl}/api/v1/devices`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n if (!listRes.ok) {\r\n return { success: false, error: `Could not list devices: ${listRes.status}` };\r\n }\r\n const listData: any = await listRes.json();\r\n const devices = listData?.devices || [];\r\n if (devices.length === 0) {\r\n return { success: false, error: 'No paired device found.' };\r\n }\r\n deviceId = devices[0].id || devices[0]._id;\r\n await this.options.storage.updateConfig({ deviceId });\r\n }\r\n\r\n // Fetch device detail (includes publicKey)\r\n const res = await fetch(`${serverUrl}/api/v1/devices/${deviceId}`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n\r\n if (!res.ok) {\r\n return { success: false, error: `Could not fetch device: ${res.status}` };\r\n }\r\n\r\n const device: any = await res.json();\r\n\r\n if (!device.publicKey) {\r\n return { success: false, error: 'Device has no public key. Enable encryption in SMSTunnel app on the phone.' };\r\n }\r\n\r\n await this.options.storage.updateConfig({\r\n deviceId,\r\n devicePublicKey: device.publicKey,\r\n deviceFingerprint: device.publicKeyFingerprint,\r\n deviceName: device.name || config.deviceName,\r\n });\r\n\r\n this.logger.log(`E2E keys exchanged. Fingerprint: ${device.publicKeyFingerprint}`);\r\n return {\r\n success: true,\r\n fingerprint: device.publicKeyFingerprint,\r\n deviceName: device.name,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`Key exchange error: ${err.message}`);\r\n return { success: false, error: `Key exchange failed: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Send encrypted SMS via SMSTunnel API.\r\n * The payload is encrypted client-side with the device's RSA public key.\r\n */\r\n async sendEncryptedSms(\r\n encryptedPayload: string,\r\n deviceId: string,\r\n is2FA: boolean = false,\r\n ): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send', {\r\n encrypted: true,\r\n encryptedPayload,\r\n deviceId,\r\n is2FA,\r\n });\r\n }\r\n\r\n /**\r\n * List pairings for the authenticated user.\r\n */\r\n async getPairings(): Promise<any> {\r\n return this.apiGet('/api/v1/pairings');\r\n }\r\n\r\n // ─── Internal helpers ──────────────────────────────\r\n\r\n /** Make an authenticated GET request to SMSTunnel */\r\n private async apiGet(path: string): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n\r\n /** Make an authenticated POST request to SMSTunnel */\r\n private async apiPost(path: string, body: any): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify(body),\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n}\r\n","export const SMSTUNNEL_OPTIONS = 'SMSTUNNEL_OPTIONS';\r\n\r\nexport const SMSTUNNEL_DEFAULT_PREFIX = 'smstunnel';\r\n\r\n/**\r\n * Public paths that should be excluded from auth guards.\r\n * Use with your global guard to skip auth on callback/polling routes.\r\n *\r\n * Example (NestJS):\r\n * if (SMSTUNNEL_PUBLIC_PATHS.some(p => request.url.includes(p))) return true;\r\n */\r\nexport const SMSTUNNEL_PUBLIC_PATHS = [\r\n '/smstunnel/pairing-status/',\r\n '/smstunnel/callback',\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n];\r\n\r\n/**\r\n * Returns public paths with a custom prefix.\r\n */\r\nexport function getSmsTunnelPublicPaths(prefix: string): string[] {\r\n return [\r\n `/${prefix}/pairing-status/`,\r\n `/${prefix}/callback`,\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n ];\r\n}\r\n","import {\r\n Controller,\r\n Post,\r\n Get,\r\n Body,\r\n Param,\r\n Inject,\r\n Logger,\r\n} from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n PairingCallbackBody,\r\n LegacyCallbackBody,\r\n} from './smstunnel.interfaces';\r\n\r\n/**\r\n * Decorator to mark routes as public (no auth).\r\n * If you have your own @Public() decorator, the module re-exports\r\n * SMSTUNNEL_PUBLIC_PATHS so you can exclude them in your guard.\r\n */\r\nfunction SmsTunnelPublic() {\r\n // We use Reflect metadata so consumers can detect public routes\r\n return (target: any, key: string, descriptor: PropertyDescriptor) => {\r\n Reflect.defineMetadata('isPublic', true, descriptor.value);\r\n return descriptor;\r\n };\r\n}\r\n\r\n@Controller()\r\nexport class SmsTunnelController {\r\n private readonly logger = new Logger(SmsTunnelController.name);\r\n private readonly prefix: string;\r\n\r\n constructor(\r\n private readonly smsTunnelService: SmsTunnelService,\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {\r\n this.prefix = 'smstunnel';\r\n }\r\n\r\n /**\r\n * Get current pairing status.\r\n * Route: GET /{prefix}/status\r\n */\r\n @Get('smstunnel/status')\r\n async getStatus() {\r\n return this.smsTunnelService.getStatus();\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel.\r\n * Route: POST /{prefix}/create-token\r\n */\r\n @Post('smstunnel/create-token')\r\n async createToken() {\r\n return this.smsTunnelService.createPairingToken();\r\n }\r\n\r\n /**\r\n * Proxy polling to SMSTunnel (avoids CORS issues).\r\n * This should be public (no auth) - just proxying SMSTunnel's endpoint.\r\n * Route: GET /{prefix}/pairing-status/:token\r\n */\r\n @SmsTunnelPublic()\r\n @Get('smstunnel/pairing-status/:token')\r\n async pairingStatus(@Param('token') token: string) {\r\n return this.smsTunnelService.getPairingStatus(token);\r\n }\r\n\r\n /**\r\n * Callback from SMSTunnel after pairing completes.\r\n * Route: POST /{prefix}/callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('smstunnel/callback')\r\n async pairingCallback(@Body() body: PairingCallbackBody) {\r\n this.logger.log(\r\n `SMSTunnel callback: event=${body.event}, token=${body.token?.substring(0, 8)}...`,\r\n );\r\n const success = await this.smsTunnelService.handlePairingCallback(body);\r\n return { success };\r\n }\r\n\r\n /**\r\n * WordPress-compatible legacy callback.\r\n * Route: POST /wp-json/smstunnel/v1/setup-callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('wp-json/smstunnel/v1/setup-callback')\r\n async setupCallbackWp(@Body() body: LegacyCallbackBody) {\r\n this.logger.log(`SMSTunnel legacy callback. Status: ${body.status || 'unknown'}`);\r\n const success = await this.smsTunnelService.handleLegacyCallback(\r\n body.site_token,\r\n body.api_key,\r\n body.device_name,\r\n );\r\n return { success };\r\n }\r\n\r\n /**\r\n * Pair using an API key directly (alternative to QR).\r\n * Route: POST /{prefix}/pair-with-key\r\n */\r\n @Post('smstunnel/pair-with-key')\r\n async pairWithKey(@Body() body: { apiKey: string }) {\r\n return this.smsTunnelService.pairWithApiKey(body.apiKey);\r\n }\r\n\r\n /**\r\n * Unpair the connected device.\r\n * Route: POST /{prefix}/unpair\r\n */\r\n @Post('smstunnel/unpair')\r\n async unpair() {\r\n await this.smsTunnelService.unpair();\r\n return { success: true };\r\n }\r\n\r\n /**\r\n * Send an SMS via SMSTunnel.\r\n * Route: POST /{prefix}/send\r\n */\r\n @Post('smstunnel/send')\r\n async sendSms(@Body() body: { to: string; message: string }) {\r\n return this.smsTunnelService.sendSms(body.to, body.message);\r\n }\r\n\r\n /**\r\n * Update server URL configuration.\r\n * Route: POST /{prefix}/update-config\r\n */\r\n @Post('smstunnel/update-config')\r\n async updateConfig(@Body() body: { serverUrl: string }) {\r\n await this.smsTunnelService.updateServerUrl(body.serverUrl);\r\n return { success: true };\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send a 2FA SMS.\r\n * Route: POST /{prefix}/send-2fa\r\n */\r\n @Post('smstunnel/send-2fa')\r\n async send2fa(@Body() body: { to: string; code: string; template?: string }) {\r\n return this.smsTunnelService.send2fa(body.to, body.code, body.template);\r\n }\r\n\r\n /**\r\n * Send bulk SMS.\r\n * Route: POST /{prefix}/send-bulk\r\n */\r\n @Post('smstunnel/send-bulk')\r\n async sendBulk(@Body() body: { messages: Array<{ to: string; message: string }> }) {\r\n return this.smsTunnelService.sendBulk(body.messages);\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n * Route: GET /{prefix}/sms-status/:messageId\r\n */\r\n @Get('smstunnel/sms-status/:messageId')\r\n async smsStatus(@Param('messageId') messageId: string) {\r\n return this.smsTunnelService.getSmsStatus(messageId);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n * Route: GET /{prefix}/received\r\n */\r\n @Get('smstunnel/received')\r\n async receivedSms() {\r\n return this.smsTunnelService.getReceivedSms();\r\n }\r\n\r\n // ─── E2E Encryption ────────────────────────────────\r\n\r\n /**\r\n * Exchange encryption keys with the paired device.\r\n * Fetches the device's public key from SMSTunnel and stores it locally.\r\n * Route: POST /{prefix}/exchange-keys\r\n */\r\n @Post('smstunnel/exchange-keys')\r\n async exchangeKeys() {\r\n return this.smsTunnelService.exchangeKeys();\r\n }\r\n\r\n /**\r\n * Send encrypted SMS.\r\n * Route: POST /{prefix}/send-encrypted\r\n */\r\n @Post('smstunnel/send-encrypted')\r\n async sendEncrypted(\r\n @Body() body: { encryptedPayload: string; deviceId: string; is2FA?: boolean },\r\n ) {\r\n return this.smsTunnelService.sendEncryptedSms(\r\n body.encryptedPayload,\r\n body.deviceId,\r\n body.is2FA,\r\n );\r\n }\r\n\r\n /**\r\n * List pairings.\r\n * Route: GET /{prefix}/pairings\r\n */\r\n @Get('smstunnel/pairings')\r\n async pairings() {\r\n return this.smsTunnelService.getPairings();\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n * Route: GET /{prefix}/devices\r\n */\r\n @Get('smstunnel/devices')\r\n async devices() {\r\n return this.smsTunnelService.getDevices();\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n * Route: GET /{prefix}/usage\r\n */\r\n @Get('smstunnel/usage')\r\n async usage() {\r\n return this.smsTunnelService.getUsage();\r\n }\r\n}\r\n"],"mappings":"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","names":["import_common","import_common"]}
|
package/dist/server/index.mjs
CHANGED
|
@@ -46,7 +46,9 @@ var SmsTunnelService = class {
|
|
|
46
46
|
return {
|
|
47
47
|
paired: !!config.apiKey,
|
|
48
48
|
serverUrl: config.serverUrl,
|
|
49
|
-
deviceName: config.deviceName
|
|
49
|
+
deviceName: config.deviceName,
|
|
50
|
+
e2eEnabled: !!config.devicePublicKey,
|
|
51
|
+
deviceFingerprint: config.deviceFingerprint
|
|
50
52
|
};
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
@@ -185,13 +187,22 @@ var SmsTunnelService = class {
|
|
|
185
187
|
);
|
|
186
188
|
return false;
|
|
187
189
|
}
|
|
188
|
-
|
|
190
|
+
const update = {
|
|
189
191
|
apiKey: body.apiKey,
|
|
190
192
|
deviceName: body.deviceName || "Android Phone"
|
|
191
|
-
}
|
|
193
|
+
};
|
|
194
|
+
if (body.deviceId) {
|
|
195
|
+
update.deviceId = body.deviceId;
|
|
196
|
+
}
|
|
197
|
+
await this.options.storage.updateConfig(update);
|
|
192
198
|
this.logger.log(
|
|
193
199
|
`SMSTunnel paired! Device: ${body.deviceName}, API key: ${body.apiKey?.substring(0, 12)}...`
|
|
194
200
|
);
|
|
201
|
+
if (body.deviceId) {
|
|
202
|
+
this.exchangeKeys().catch(
|
|
203
|
+
(err) => this.logger.warn(`Auto key exchange failed: ${err.message}`)
|
|
204
|
+
);
|
|
205
|
+
}
|
|
195
206
|
return true;
|
|
196
207
|
}
|
|
197
208
|
/**
|
|
@@ -209,6 +220,46 @@ var SmsTunnelService = class {
|
|
|
209
220
|
this.logger.log(`SMSTunnel paired (legacy). Device: ${deviceName || "unknown"}`);
|
|
210
221
|
return true;
|
|
211
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Pair using an API key directly (alternative to QR).
|
|
225
|
+
* Validates the key by calling /api/v1/devices, then saves it.
|
|
226
|
+
*/
|
|
227
|
+
async pairWithApiKey(apiKey) {
|
|
228
|
+
const config = await this.options.storage.getConfig();
|
|
229
|
+
if (!config.serverUrl) {
|
|
230
|
+
return { success: false, error: "SMSTunnel server URL is not configured." };
|
|
231
|
+
}
|
|
232
|
+
const serverUrl = config.serverUrl.replace(/\/$/, "");
|
|
233
|
+
try {
|
|
234
|
+
const res = await fetch(`${serverUrl}/api/v1/devices`, {
|
|
235
|
+
headers: { "X-API-Key": apiKey }
|
|
236
|
+
});
|
|
237
|
+
if (!res.ok) {
|
|
238
|
+
if (res.status === 401 || res.status === 403) {
|
|
239
|
+
return { success: false, error: "API key invalid or expired." };
|
|
240
|
+
}
|
|
241
|
+
return { success: false, error: `SMSTunnel returned error: ${res.status}` };
|
|
242
|
+
}
|
|
243
|
+
const data = await res.json();
|
|
244
|
+
const devices = data?.devices || [];
|
|
245
|
+
const device = devices[0];
|
|
246
|
+
const deviceName = device?.name || "Android Phone";
|
|
247
|
+
const deviceId = device?.id || device?._id;
|
|
248
|
+
const update = { apiKey, deviceName };
|
|
249
|
+
if (deviceId) update.deviceId = deviceId;
|
|
250
|
+
await this.options.storage.updateConfig(update);
|
|
251
|
+
this.logger.log(`Paired via API key. Device: ${deviceName}`);
|
|
252
|
+
if (deviceId) {
|
|
253
|
+
this.exchangeKeys().catch(
|
|
254
|
+
(err) => this.logger.warn(`Auto key exchange failed: ${err.message}`)
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return { success: true, deviceName };
|
|
258
|
+
} catch (err) {
|
|
259
|
+
this.logger.error(`Pair with API key error: ${err.message}`);
|
|
260
|
+
return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
212
263
|
/**
|
|
213
264
|
* Send SMS via SMSTunnel API.
|
|
214
265
|
*/
|
|
@@ -305,35 +356,59 @@ var SmsTunnelService = class {
|
|
|
305
356
|
}
|
|
306
357
|
// ─── E2E Encryption ──────────────────────────────
|
|
307
358
|
/**
|
|
308
|
-
*
|
|
359
|
+
* Exchange encryption keys with the paired device.
|
|
360
|
+
* Fetches the device's public key from SMSTunnel server and stores it locally.
|
|
361
|
+
* Called automatically after pairing, or manually via POST /smstunnel/exchange-keys.
|
|
309
362
|
*/
|
|
310
|
-
async
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
* Get device public key for E2E encryption.
|
|
315
|
-
*/
|
|
316
|
-
async getDevicePublicKey(deviceId) {
|
|
317
|
-
return this.apiGet(`/api/v1/devices/${deviceId}/public-key`);
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Verify that a stored fingerprint matches the current device key.
|
|
321
|
-
*/
|
|
322
|
-
async verifyDeviceKey(deviceId, storedFingerprint) {
|
|
323
|
-
const res = await this.getDeviceE2EStatus(deviceId);
|
|
324
|
-
if (!res.success || !res.data) {
|
|
325
|
-
return { valid: false, needsRePairing: true };
|
|
363
|
+
async exchangeKeys() {
|
|
364
|
+
const config = await this.options.storage.getConfig();
|
|
365
|
+
if (!config.apiKey || !config.serverUrl) {
|
|
366
|
+
return { success: false, error: "SMSTunnel is not configured." };
|
|
326
367
|
}
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
368
|
+
const serverUrl = config.serverUrl.replace(/\/$/, "");
|
|
369
|
+
try {
|
|
370
|
+
let deviceId = config.deviceId;
|
|
371
|
+
if (!deviceId) {
|
|
372
|
+
const listRes = await fetch(`${serverUrl}/api/v1/devices`, {
|
|
373
|
+
headers: { "X-API-Key": config.apiKey }
|
|
374
|
+
});
|
|
375
|
+
if (!listRes.ok) {
|
|
376
|
+
return { success: false, error: `Could not list devices: ${listRes.status}` };
|
|
377
|
+
}
|
|
378
|
+
const listData = await listRes.json();
|
|
379
|
+
const devices = listData?.devices || [];
|
|
380
|
+
if (devices.length === 0) {
|
|
381
|
+
return { success: false, error: "No paired device found." };
|
|
382
|
+
}
|
|
383
|
+
deviceId = devices[0].id || devices[0]._id;
|
|
384
|
+
await this.options.storage.updateConfig({ deviceId });
|
|
385
|
+
}
|
|
386
|
+
const res = await fetch(`${serverUrl}/api/v1/devices/${deviceId}`, {
|
|
387
|
+
headers: { "X-API-Key": config.apiKey }
|
|
388
|
+
});
|
|
389
|
+
if (!res.ok) {
|
|
390
|
+
return { success: false, error: `Could not fetch device: ${res.status}` };
|
|
391
|
+
}
|
|
392
|
+
const device = await res.json();
|
|
393
|
+
if (!device.publicKey) {
|
|
394
|
+
return { success: false, error: "Device has no public key. Enable encryption in SMSTunnel app on the phone." };
|
|
395
|
+
}
|
|
396
|
+
await this.options.storage.updateConfig({
|
|
397
|
+
deviceId,
|
|
398
|
+
devicePublicKey: device.publicKey,
|
|
399
|
+
deviceFingerprint: device.publicKeyFingerprint,
|
|
400
|
+
deviceName: device.name || config.deviceName
|
|
401
|
+
});
|
|
402
|
+
this.logger.log(`E2E keys exchanged. Fingerprint: ${device.publicKeyFingerprint}`);
|
|
403
|
+
return {
|
|
404
|
+
success: true,
|
|
405
|
+
fingerprint: device.publicKeyFingerprint,
|
|
406
|
+
deviceName: device.name
|
|
407
|
+
};
|
|
408
|
+
} catch (err) {
|
|
409
|
+
this.logger.error(`Key exchange error: ${err.message}`);
|
|
410
|
+
return { success: false, error: `Key exchange failed: ${err.message}` };
|
|
330
411
|
}
|
|
331
|
-
const matches = status.publicKeyFingerprint === storedFingerprint;
|
|
332
|
-
return {
|
|
333
|
-
valid: matches,
|
|
334
|
-
currentFingerprint: status.publicKeyFingerprint,
|
|
335
|
-
needsRePairing: !matches
|
|
336
|
-
};
|
|
337
412
|
}
|
|
338
413
|
/**
|
|
339
414
|
* Send encrypted SMS via SMSTunnel API.
|
|
@@ -445,6 +520,9 @@ var SmsTunnelController = class {
|
|
|
445
520
|
);
|
|
446
521
|
return { success };
|
|
447
522
|
}
|
|
523
|
+
async pairWithKey(body) {
|
|
524
|
+
return this.smsTunnelService.pairWithApiKey(body.apiKey);
|
|
525
|
+
}
|
|
448
526
|
async unpair() {
|
|
449
527
|
await this.smsTunnelService.unpair();
|
|
450
528
|
return { success: true };
|
|
@@ -468,14 +546,8 @@ var SmsTunnelController = class {
|
|
|
468
546
|
async receivedSms() {
|
|
469
547
|
return this.smsTunnelService.getReceivedSms();
|
|
470
548
|
}
|
|
471
|
-
async
|
|
472
|
-
return this.smsTunnelService.
|
|
473
|
-
}
|
|
474
|
-
async publicKey(deviceId) {
|
|
475
|
-
return this.smsTunnelService.getDevicePublicKey(deviceId);
|
|
476
|
-
}
|
|
477
|
-
async verifyKey(body) {
|
|
478
|
-
return this.smsTunnelService.verifyDeviceKey(body.deviceId, body.fingerprint);
|
|
549
|
+
async exchangeKeys() {
|
|
550
|
+
return this.smsTunnelService.exchangeKeys();
|
|
479
551
|
}
|
|
480
552
|
async sendEncrypted(body) {
|
|
481
553
|
return this.smsTunnelService.sendEncryptedSms(
|
|
@@ -515,6 +587,10 @@ __decorateClass([
|
|
|
515
587
|
Post("wp-json/smstunnel/v1/setup-callback"),
|
|
516
588
|
__decorateParam(0, Body())
|
|
517
589
|
], SmsTunnelController.prototype, "setupCallbackWp", 1);
|
|
590
|
+
__decorateClass([
|
|
591
|
+
Post("smstunnel/pair-with-key"),
|
|
592
|
+
__decorateParam(0, Body())
|
|
593
|
+
], SmsTunnelController.prototype, "pairWithKey", 1);
|
|
518
594
|
__decorateClass([
|
|
519
595
|
Post("smstunnel/unpair")
|
|
520
596
|
], SmsTunnelController.prototype, "unpair", 1);
|
|
@@ -542,17 +618,8 @@ __decorateClass([
|
|
|
542
618
|
Get("smstunnel/received")
|
|
543
619
|
], SmsTunnelController.prototype, "receivedSms", 1);
|
|
544
620
|
__decorateClass([
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
], SmsTunnelController.prototype, "e2eStatus", 1);
|
|
548
|
-
__decorateClass([
|
|
549
|
-
Get("smstunnel/public-key/:deviceId"),
|
|
550
|
-
__decorateParam(0, Param("deviceId"))
|
|
551
|
-
], SmsTunnelController.prototype, "publicKey", 1);
|
|
552
|
-
__decorateClass([
|
|
553
|
-
Post("smstunnel/verify-key"),
|
|
554
|
-
__decorateParam(0, Body())
|
|
555
|
-
], SmsTunnelController.prototype, "verifyKey", 1);
|
|
621
|
+
Post("smstunnel/exchange-keys")
|
|
622
|
+
], SmsTunnelController.prototype, "exchangeKeys", 1);
|
|
556
623
|
__decorateClass([
|
|
557
624
|
Post("smstunnel/send-encrypted"),
|
|
558
625
|
__decorateParam(0, Body())
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/smstunnel.module.ts","../../src/server/smstunnel.service.ts","../../src/server/smstunnel.constants.ts","../../src/server/smstunnel.controller.ts"],"sourcesContent":["import { DynamicModule, Module, Provider } from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SmsTunnelController } from './smstunnel.controller';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelModuleAsyncOptions,\r\n} from './smstunnel.interfaces';\r\n\r\n@Module({})\r\nexport class SmsTunnelModule {\r\n static forRoot(options: SmsTunnelModuleOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useValue: options,\r\n };\r\n\r\n const controllers = [SmsTunnelController];\r\n if (!options.enableLegacyCallback) {\r\n // Legacy callback is always registered but won't match\r\n // if the consumer doesn't enable it - no-op\r\n }\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers,\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n\r\n static forRootAsync(asyncOptions: SmsTunnelModuleAsyncOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useFactory: asyncOptions.useFactory,\r\n inject: asyncOptions.inject || [],\r\n };\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers: [SmsTunnelController],\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n}\r\n","import { Inject, Injectable, Logger } from '@nestjs/common';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelStatus,\r\n SendSmsResult,\r\n CreateTokenResult,\r\n PairingStatusResult,\r\n PairingCallbackBody,\r\n SaasActivateResult,\r\n DeviceE2EStatus,\r\n DevicePublicKeyInfo,\r\n VerifyDeviceKeyResult,\r\n} from './smstunnel.interfaces';\r\n\r\n@Injectable()\r\nexport class SmsTunnelService {\r\n private readonly logger = new Logger(SmsTunnelService.name);\r\n\r\n constructor(\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {}\r\n\r\n /**\r\n * Get current pairing status.\r\n */\r\n async getStatus(): Promise<SmsTunnelStatus> {\r\n const config = await this.options.storage.getConfig();\r\n return {\r\n paired: !!config.apiKey,\r\n serverUrl: config.serverUrl,\r\n deviceName: config.deviceName,\r\n };\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel server.\r\n * Uses Enterprise/SaaS flow when enterpriseApiKey is configured,\r\n * otherwise falls back to public-create flow.\r\n */\r\n async createPairingToken(): Promise<CreateTokenResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel server URL is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n if (this.options.enterpriseApiKey) {\r\n return this.createPairingTokenEnterprise(serverUrl);\r\n }\r\n\r\n return this.createPairingTokenPublic(serverUrl);\r\n }\r\n\r\n /**\r\n * Public flow: POST /api/v1/pairing/public-create\r\n */\r\n private async createPairingTokenPublic(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/public-create`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n source: 'api',\r\n displayName: this.options.displayName || 'App',\r\n displayUrl: this.options.displayUrl || this.options.callbackBaseUrl,\r\n context: { callbackUrl },\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel create token failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: any = await res.json();\r\n\r\n await this.options.storage.updateConfig({ siteToken: data.token });\r\n\r\n this.logger.log(`Pairing token created: ${data.token?.substring(0, 12)}...`);\r\n\r\n return {\r\n success: true,\r\n token: data.token,\r\n qrData: data.qrData,\r\n expiresAt: data.expiresAt,\r\n pollUrl: data.pollUrl || `${serverUrl}/api/v1/pairing/${data.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel create token error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Enterprise/SaaS flow: POST /api/v1/saas/activate\r\n * Creates sub-accounts under the Enterprise account.\r\n */\r\n private async createPairingTokenEnterprise(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/saas/activate`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': this.options.enterpriseApiKey!,\r\n },\r\n body: JSON.stringify({\r\n clientName: this.options.displayName || 'App',\r\n externalId: this.options.externalId || 'default',\r\n callbackUrl,\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel SaaS activate failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: SaasActivateResult = await res.json();\r\n\r\n if (!data.success || !data.activation) {\r\n return { success: false, error: data.error || 'SaaS activation failed' };\r\n }\r\n\r\n // Save the activation token as siteToken\r\n const configUpdate: Record<string, any> = { siteToken: data.activation.token };\r\n\r\n // If the server returned an API key, save it too\r\n if (data.apiKey?.key) {\r\n configUpdate.apiKey = data.apiKey.key;\r\n }\r\n\r\n await this.options.storage.updateConfig(configUpdate);\r\n\r\n this.logger.log(\r\n `SaaS pairing token created: ${data.activation.token?.substring(0, 12)}... (isNew: ${data.isNew})`,\r\n );\r\n\r\n return {\r\n success: true,\r\n token: data.activation.token,\r\n qrData: data.activation.qrData,\r\n expiresAt: data.activation.expiresAt,\r\n pollUrl: `${serverUrl}/api/v1/pairing/${data.activation.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel SaaS activate error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Check pairing status by polling SMSTunnel.\r\n */\r\n async getPairingStatus(token: string): Promise<PairingStatusResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { status: 'error', error: 'SMSTunnel server URL not configured' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/${token}`);\r\n if (!res.ok) {\r\n return { status: 'expired' };\r\n }\r\n return (await res.json()) as PairingStatusResult;\r\n } catch {\r\n return { status: 'error', error: 'Could not contact SMSTunnel' };\r\n }\r\n }\r\n\r\n /**\r\n * Handle callback from SMSTunnel after pairing completes.\r\n */\r\n async handlePairingCallback(body: PairingCallbackBody): Promise<boolean> {\r\n if (body.event !== 'pairing_completed') {\r\n this.logger.warn(`Unknown callback event: ${body.event}`);\r\n return false;\r\n }\r\n\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken && config.siteToken !== body.token) {\r\n this.logger.warn(\r\n `Callback token mismatch: expected ${config.siteToken?.substring(0, 8)}, got ${body.token?.substring(0, 8)}`,\r\n );\r\n return false;\r\n }\r\n\r\n await this.options.storage.updateConfig({\r\n apiKey: body.apiKey,\r\n deviceName: body.deviceName || 'Android Phone',\r\n });\r\n\r\n this.logger.log(\r\n `SMSTunnel paired! Device: ${body.deviceName}, API key: ${body.apiKey?.substring(0, 12)}...`,\r\n );\r\n return true;\r\n }\r\n\r\n /**\r\n * Handle legacy WordPress-compatible callback.\r\n */\r\n async handleLegacyCallback(\r\n siteToken: string,\r\n apiKey: string,\r\n deviceName?: string,\r\n ): Promise<boolean> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken !== siteToken) {\r\n this.logger.warn('Legacy callback: site_token mismatch');\r\n return false;\r\n }\r\n\r\n const update: Partial<{ apiKey: string; deviceName: string }> = { apiKey };\r\n if (deviceName) update.deviceName = deviceName;\r\n\r\n await this.options.storage.updateConfig(update);\r\n this.logger.log(`SMSTunnel paired (legacy). Device: ${deviceName || 'unknown'}`);\r\n return true;\r\n }\r\n\r\n /**\r\n * Send SMS via SMSTunnel API.\r\n */\r\n async sendSms(to: string, message: string): Promise<SendSmsResult> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (!config.apiKey) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel is not configured. Pair a device first.',\r\n };\r\n }\r\n\r\n if (!config.serverUrl) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel server URL is not configured.',\r\n };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/sms/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify({ to, message }),\r\n });\r\n\r\n const data: any = await res.json();\r\n\r\n if (data.success) {\r\n this.logger.log(`SMS sent via SMSTunnel: ${data.messageId} -> ${to}`);\r\n return { success: true, messageId: data.messageId };\r\n }\r\n\r\n return {\r\n success: false,\r\n error: data.error || data.message || 'SMSTunnel returned an error',\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMS send error: ${err.message}`);\r\n return { success: false, error: `Could not send SMS: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Unpair - clear saved API key and device info.\r\n */\r\n async unpair(): Promise<void> {\r\n await this.options.storage.clearConfig();\r\n this.logger.log('SMSTunnel unpaired');\r\n }\r\n\r\n /**\r\n * Update server URL in config.\r\n */\r\n async updateServerUrl(serverUrl: string): Promise<void> {\r\n await this.options.storage.updateConfig({\r\n serverUrl: serverUrl.replace(/\\/$/, ''),\r\n });\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send 2FA SMS via SMSTunnel API.\r\n */\r\n async send2fa(to: string, code: string, template?: string): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send-2fa', { to, code, template });\r\n }\r\n\r\n /**\r\n * Send bulk SMS via SMSTunnel API.\r\n */\r\n async sendBulk(\r\n messages: Array<{ to: string; message: string }>,\r\n ): Promise<{ success: boolean; results?: any[]; error?: string }> {\r\n return this.apiPost('/api/v1/sms/send-bulk', { messages });\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n */\r\n async getSmsStatus(messageId: string): Promise<any> {\r\n return this.apiGet(`/api/v1/sms/status/${messageId}`);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n */\r\n async getReceivedSms(): Promise<any> {\r\n return this.apiGet('/api/v1/sms/received');\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n */\r\n async getDevices(): Promise<any> {\r\n return this.apiGet('/api/v1/devices');\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n */\r\n async getUsage(): Promise<any> {\r\n return this.apiGet('/api/v1/account/usage');\r\n }\r\n\r\n // ─── E2E Encryption ──────────────────────────────\r\n\r\n /**\r\n * Get E2E encryption status for a device.\r\n */\r\n async getDeviceE2EStatus(deviceId: string): Promise<{ success: boolean; data?: DeviceE2EStatus; error?: string }> {\r\n return this.apiGet(`/api/v1/devices/${deviceId}/e2e-status`);\r\n }\r\n\r\n /**\r\n * Get device public key for E2E encryption.\r\n */\r\n async getDevicePublicKey(deviceId: string): Promise<{ success: boolean; data?: DevicePublicKeyInfo; error?: string }> {\r\n return this.apiGet(`/api/v1/devices/${deviceId}/public-key`);\r\n }\r\n\r\n /**\r\n * Verify that a stored fingerprint matches the current device key.\r\n */\r\n async verifyDeviceKey(deviceId: string, storedFingerprint: string): Promise<VerifyDeviceKeyResult> {\r\n const res = await this.getDeviceE2EStatus(deviceId);\r\n if (!res.success || !res.data) {\r\n return { valid: false, needsRePairing: true };\r\n }\r\n const status = res.data;\r\n if (!status.hasPublicKey || !status.publicKeyFingerprint) {\r\n return { valid: false, needsRePairing: true };\r\n }\r\n const matches = status.publicKeyFingerprint === storedFingerprint;\r\n return {\r\n valid: matches,\r\n currentFingerprint: status.publicKeyFingerprint,\r\n needsRePairing: !matches,\r\n };\r\n }\r\n\r\n /**\r\n * Send encrypted SMS via SMSTunnel API.\r\n * The payload is encrypted client-side with the device's RSA public key.\r\n */\r\n async sendEncryptedSms(\r\n encryptedPayload: string,\r\n deviceId: string,\r\n is2FA: boolean = false,\r\n ): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send', {\r\n encrypted: true,\r\n encryptedPayload,\r\n deviceId,\r\n is2FA,\r\n });\r\n }\r\n\r\n /**\r\n * List pairings for the authenticated user.\r\n */\r\n async getPairings(): Promise<any> {\r\n return this.apiGet('/api/v1/pairings');\r\n }\r\n\r\n // ─── Internal helpers ──────────────────────────────\r\n\r\n /** Make an authenticated GET request to SMSTunnel */\r\n private async apiGet(path: string): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n\r\n /** Make an authenticated POST request to SMSTunnel */\r\n private async apiPost(path: string, body: any): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify(body),\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n}\r\n","export const SMSTUNNEL_OPTIONS = 'SMSTUNNEL_OPTIONS';\r\n\r\nexport const SMSTUNNEL_DEFAULT_PREFIX = 'smstunnel';\r\n\r\n/**\r\n * Public paths that should be excluded from auth guards.\r\n * Use with your global guard to skip auth on callback/polling routes.\r\n *\r\n * Example (NestJS):\r\n * if (SMSTUNNEL_PUBLIC_PATHS.some(p => request.url.includes(p))) return true;\r\n */\r\nexport const SMSTUNNEL_PUBLIC_PATHS = [\r\n '/smstunnel/pairing-status/',\r\n '/smstunnel/callback',\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n];\r\n\r\n/**\r\n * Returns public paths with a custom prefix.\r\n */\r\nexport function getSmsTunnelPublicPaths(prefix: string): string[] {\r\n return [\r\n `/${prefix}/pairing-status/`,\r\n `/${prefix}/callback`,\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n ];\r\n}\r\n","import {\r\n Controller,\r\n Post,\r\n Get,\r\n Body,\r\n Param,\r\n Inject,\r\n Logger,\r\n} from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n PairingCallbackBody,\r\n LegacyCallbackBody,\r\n} from './smstunnel.interfaces';\r\n\r\n/**\r\n * Decorator to mark routes as public (no auth).\r\n * If you have your own @Public() decorator, the module re-exports\r\n * SMSTUNNEL_PUBLIC_PATHS so you can exclude them in your guard.\r\n */\r\nfunction SmsTunnelPublic() {\r\n // We use Reflect metadata so consumers can detect public routes\r\n return (target: any, key: string, descriptor: PropertyDescriptor) => {\r\n Reflect.defineMetadata('isPublic', true, descriptor.value);\r\n return descriptor;\r\n };\r\n}\r\n\r\n@Controller()\r\nexport class SmsTunnelController {\r\n private readonly logger = new Logger(SmsTunnelController.name);\r\n private readonly prefix: string;\r\n\r\n constructor(\r\n private readonly smsTunnelService: SmsTunnelService,\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {\r\n this.prefix = 'smstunnel';\r\n }\r\n\r\n /**\r\n * Get current pairing status.\r\n * Route: GET /{prefix}/status\r\n */\r\n @Get('smstunnel/status')\r\n async getStatus() {\r\n return this.smsTunnelService.getStatus();\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel.\r\n * Route: POST /{prefix}/create-token\r\n */\r\n @Post('smstunnel/create-token')\r\n async createToken() {\r\n return this.smsTunnelService.createPairingToken();\r\n }\r\n\r\n /**\r\n * Proxy polling to SMSTunnel (avoids CORS issues).\r\n * This should be public (no auth) - just proxying SMSTunnel's endpoint.\r\n * Route: GET /{prefix}/pairing-status/:token\r\n */\r\n @SmsTunnelPublic()\r\n @Get('smstunnel/pairing-status/:token')\r\n async pairingStatus(@Param('token') token: string) {\r\n return this.smsTunnelService.getPairingStatus(token);\r\n }\r\n\r\n /**\r\n * Callback from SMSTunnel after pairing completes.\r\n * Route: POST /{prefix}/callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('smstunnel/callback')\r\n async pairingCallback(@Body() body: PairingCallbackBody) {\r\n this.logger.log(\r\n `SMSTunnel callback: event=${body.event}, token=${body.token?.substring(0, 8)}...`,\r\n );\r\n const success = await this.smsTunnelService.handlePairingCallback(body);\r\n return { success };\r\n }\r\n\r\n /**\r\n * WordPress-compatible legacy callback.\r\n * Route: POST /wp-json/smstunnel/v1/setup-callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('wp-json/smstunnel/v1/setup-callback')\r\n async setupCallbackWp(@Body() body: LegacyCallbackBody) {\r\n this.logger.log(`SMSTunnel legacy callback. Status: ${body.status || 'unknown'}`);\r\n const success = await this.smsTunnelService.handleLegacyCallback(\r\n body.site_token,\r\n body.api_key,\r\n body.device_name,\r\n );\r\n return { success };\r\n }\r\n\r\n /**\r\n * Unpair the connected device.\r\n * Route: POST /{prefix}/unpair\r\n */\r\n @Post('smstunnel/unpair')\r\n async unpair() {\r\n await this.smsTunnelService.unpair();\r\n return { success: true };\r\n }\r\n\r\n /**\r\n * Send an SMS via SMSTunnel.\r\n * Route: POST /{prefix}/send\r\n */\r\n @Post('smstunnel/send')\r\n async sendSms(@Body() body: { to: string; message: string }) {\r\n return this.smsTunnelService.sendSms(body.to, body.message);\r\n }\r\n\r\n /**\r\n * Update server URL configuration.\r\n * Route: POST /{prefix}/update-config\r\n */\r\n @Post('smstunnel/update-config')\r\n async updateConfig(@Body() body: { serverUrl: string }) {\r\n await this.smsTunnelService.updateServerUrl(body.serverUrl);\r\n return { success: true };\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send a 2FA SMS.\r\n * Route: POST /{prefix}/send-2fa\r\n */\r\n @Post('smstunnel/send-2fa')\r\n async send2fa(@Body() body: { to: string; code: string; template?: string }) {\r\n return this.smsTunnelService.send2fa(body.to, body.code, body.template);\r\n }\r\n\r\n /**\r\n * Send bulk SMS.\r\n * Route: POST /{prefix}/send-bulk\r\n */\r\n @Post('smstunnel/send-bulk')\r\n async sendBulk(@Body() body: { messages: Array<{ to: string; message: string }> }) {\r\n return this.smsTunnelService.sendBulk(body.messages);\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n * Route: GET /{prefix}/sms-status/:messageId\r\n */\r\n @Get('smstunnel/sms-status/:messageId')\r\n async smsStatus(@Param('messageId') messageId: string) {\r\n return this.smsTunnelService.getSmsStatus(messageId);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n * Route: GET /{prefix}/received\r\n */\r\n @Get('smstunnel/received')\r\n async receivedSms() {\r\n return this.smsTunnelService.getReceivedSms();\r\n }\r\n\r\n // ─── E2E Encryption ────────────────────────────────\r\n\r\n /**\r\n * Get E2E encryption status for a device.\r\n * Route: GET /{prefix}/e2e-status/:deviceId\r\n */\r\n @Get('smstunnel/e2e-status/:deviceId')\r\n async e2eStatus(@Param('deviceId') deviceId: string) {\r\n return this.smsTunnelService.getDeviceE2EStatus(deviceId);\r\n }\r\n\r\n /**\r\n * Get device public key for E2E encryption.\r\n * Route: GET /{prefix}/public-key/:deviceId\r\n */\r\n @Get('smstunnel/public-key/:deviceId')\r\n async publicKey(@Param('deviceId') deviceId: string) {\r\n return this.smsTunnelService.getDevicePublicKey(deviceId);\r\n }\r\n\r\n /**\r\n * Verify device key fingerprint.\r\n * Route: POST /{prefix}/verify-key\r\n */\r\n @Post('smstunnel/verify-key')\r\n async verifyKey(@Body() body: { deviceId: string; fingerprint: string }) {\r\n return this.smsTunnelService.verifyDeviceKey(body.deviceId, body.fingerprint);\r\n }\r\n\r\n /**\r\n * Send encrypted SMS.\r\n * Route: POST /{prefix}/send-encrypted\r\n */\r\n @Post('smstunnel/send-encrypted')\r\n async sendEncrypted(\r\n @Body() body: { encryptedPayload: string; deviceId: string; is2FA?: boolean },\r\n ) {\r\n return this.smsTunnelService.sendEncryptedSms(\r\n body.encryptedPayload,\r\n body.deviceId,\r\n body.is2FA,\r\n );\r\n }\r\n\r\n /**\r\n * List pairings.\r\n * Route: GET /{prefix}/pairings\r\n */\r\n @Get('smstunnel/pairings')\r\n async pairings() {\r\n return this.smsTunnelService.getPairings();\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n * Route: GET /{prefix}/devices\r\n */\r\n @Get('smstunnel/devices')\r\n async devices() {\r\n return this.smsTunnelService.getDevices();\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n * Route: GET /{prefix}/usage\r\n */\r\n @Get('smstunnel/usage')\r\n async usage() {\r\n return this.smsTunnelService.getUsage();\r\n }\r\n}\r\n"],"mappings":"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","names":["Inject","Logger","Logger","Inject"]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/smstunnel.module.ts","../../src/server/smstunnel.service.ts","../../src/server/smstunnel.constants.ts","../../src/server/smstunnel.controller.ts"],"sourcesContent":["import { DynamicModule, Module, Provider } from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SmsTunnelController } from './smstunnel.controller';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelModuleAsyncOptions,\r\n} from './smstunnel.interfaces';\r\n\r\n@Module({})\r\nexport class SmsTunnelModule {\r\n static forRoot(options: SmsTunnelModuleOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useValue: options,\r\n };\r\n\r\n const controllers = [SmsTunnelController];\r\n if (!options.enableLegacyCallback) {\r\n // Legacy callback is always registered but won't match\r\n // if the consumer doesn't enable it - no-op\r\n }\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers,\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n\r\n static forRootAsync(asyncOptions: SmsTunnelModuleAsyncOptions): DynamicModule {\r\n const optionsProvider: Provider = {\r\n provide: SMSTUNNEL_OPTIONS,\r\n useFactory: asyncOptions.useFactory,\r\n inject: asyncOptions.inject || [],\r\n };\r\n\r\n return {\r\n module: SmsTunnelModule,\r\n controllers: [SmsTunnelController],\r\n providers: [optionsProvider, SmsTunnelService],\r\n exports: [SmsTunnelService],\r\n global: false,\r\n };\r\n }\r\n}\r\n","import { Inject, Injectable, Logger } from '@nestjs/common';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n SmsTunnelStatus,\r\n SendSmsResult,\r\n CreateTokenResult,\r\n PairingStatusResult,\r\n PairingCallbackBody,\r\n PairWithApiKeyResult,\r\n ExchangeKeysResult,\r\n SaasActivateResult,\r\n} from './smstunnel.interfaces';\r\n\r\n@Injectable()\r\nexport class SmsTunnelService {\r\n private readonly logger = new Logger(SmsTunnelService.name);\r\n\r\n constructor(\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {}\r\n\r\n /**\r\n * Get current pairing status.\r\n */\r\n async getStatus(): Promise<SmsTunnelStatus> {\r\n const config = await this.options.storage.getConfig();\r\n return {\r\n paired: !!config.apiKey,\r\n serverUrl: config.serverUrl,\r\n deviceName: config.deviceName,\r\n e2eEnabled: !!config.devicePublicKey,\r\n deviceFingerprint: config.deviceFingerprint,\r\n };\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel server.\r\n * Uses Enterprise/SaaS flow when enterpriseApiKey is configured,\r\n * otherwise falls back to public-create flow.\r\n */\r\n async createPairingToken(): Promise<CreateTokenResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel server URL is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n if (this.options.enterpriseApiKey) {\r\n return this.createPairingTokenEnterprise(serverUrl);\r\n }\r\n\r\n return this.createPairingTokenPublic(serverUrl);\r\n }\r\n\r\n /**\r\n * Public flow: POST /api/v1/pairing/public-create\r\n */\r\n private async createPairingTokenPublic(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/public-create`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n source: 'api',\r\n displayName: this.options.displayName || 'App',\r\n displayUrl: this.options.displayUrl || this.options.callbackBaseUrl,\r\n context: { callbackUrl },\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel create token failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: any = await res.json();\r\n\r\n await this.options.storage.updateConfig({ siteToken: data.token });\r\n\r\n this.logger.log(`Pairing token created: ${data.token?.substring(0, 12)}...`);\r\n\r\n return {\r\n success: true,\r\n token: data.token,\r\n qrData: data.qrData,\r\n expiresAt: data.expiresAt,\r\n pollUrl: data.pollUrl || `${serverUrl}/api/v1/pairing/${data.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel create token error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Enterprise/SaaS flow: POST /api/v1/saas/activate\r\n * Creates sub-accounts under the Enterprise account.\r\n */\r\n private async createPairingTokenEnterprise(serverUrl: string): Promise<CreateTokenResult> {\r\n const prefix = 'smstunnel';\r\n const callbackUrl = `${this.options.callbackBaseUrl.replace(/\\/$/, '')}/${prefix}/callback`;\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/saas/activate`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': this.options.enterpriseApiKey!,\r\n },\r\n body: JSON.stringify({\r\n clientName: this.options.displayName || 'App',\r\n externalId: this.options.externalId || 'default',\r\n callbackUrl,\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const errText = await res.text();\r\n this.logger.warn(`SMSTunnel SaaS activate failed: ${res.status} ${errText}`);\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: SaasActivateResult = await res.json();\r\n\r\n if (!data.success || !data.activation) {\r\n return { success: false, error: data.error || 'SaaS activation failed' };\r\n }\r\n\r\n // Save the activation token as siteToken\r\n const configUpdate: Record<string, any> = { siteToken: data.activation.token };\r\n\r\n // If the server returned an API key, save it too\r\n if (data.apiKey?.key) {\r\n configUpdate.apiKey = data.apiKey.key;\r\n }\r\n\r\n await this.options.storage.updateConfig(configUpdate);\r\n\r\n this.logger.log(\r\n `SaaS pairing token created: ${data.activation.token?.substring(0, 12)}... (isNew: ${data.isNew})`,\r\n );\r\n\r\n return {\r\n success: true,\r\n token: data.activation.token,\r\n qrData: data.activation.qrData,\r\n expiresAt: data.activation.expiresAt,\r\n pollUrl: `${serverUrl}/api/v1/pairing/${data.activation.token}`,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMSTunnel SaaS activate error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Check pairing status by polling SMSTunnel.\r\n */\r\n async getPairingStatus(token: string): Promise<PairingStatusResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { status: 'error', error: 'SMSTunnel server URL not configured' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/pairing/${token}`);\r\n if (!res.ok) {\r\n return { status: 'expired' };\r\n }\r\n return (await res.json()) as PairingStatusResult;\r\n } catch {\r\n return { status: 'error', error: 'Could not contact SMSTunnel' };\r\n }\r\n }\r\n\r\n /**\r\n * Handle callback from SMSTunnel after pairing completes.\r\n */\r\n async handlePairingCallback(body: PairingCallbackBody): Promise<boolean> {\r\n if (body.event !== 'pairing_completed') {\r\n this.logger.warn(`Unknown callback event: ${body.event}`);\r\n return false;\r\n }\r\n\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken && config.siteToken !== body.token) {\r\n this.logger.warn(\r\n `Callback token mismatch: expected ${config.siteToken?.substring(0, 8)}, got ${body.token?.substring(0, 8)}`,\r\n );\r\n return false;\r\n }\r\n\r\n const update: Record<string, any> = {\r\n apiKey: body.apiKey,\r\n deviceName: body.deviceName || 'Android Phone',\r\n };\r\n if (body.deviceId) {\r\n update.deviceId = body.deviceId;\r\n }\r\n await this.options.storage.updateConfig(update);\r\n\r\n this.logger.log(\r\n `SMSTunnel paired! Device: ${body.deviceName}, API key: ${body.apiKey?.substring(0, 12)}...`,\r\n );\r\n\r\n // Auto-exchange keys after pairing\r\n if (body.deviceId) {\r\n this.exchangeKeys().catch((err) =>\r\n this.logger.warn(`Auto key exchange failed: ${err.message}`),\r\n );\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Handle legacy WordPress-compatible callback.\r\n */\r\n async handleLegacyCallback(\r\n siteToken: string,\r\n apiKey: string,\r\n deviceName?: string,\r\n ): Promise<boolean> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (config.siteToken !== siteToken) {\r\n this.logger.warn('Legacy callback: site_token mismatch');\r\n return false;\r\n }\r\n\r\n const update: Partial<{ apiKey: string; deviceName: string }> = { apiKey };\r\n if (deviceName) update.deviceName = deviceName;\r\n\r\n await this.options.storage.updateConfig(update);\r\n this.logger.log(`SMSTunnel paired (legacy). Device: ${deviceName || 'unknown'}`);\r\n return true;\r\n }\r\n\r\n /**\r\n * Pair using an API key directly (alternative to QR).\r\n * Validates the key by calling /api/v1/devices, then saves it.\r\n */\r\n async pairWithApiKey(apiKey: string): Promise<PairWithApiKeyResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel server URL is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/devices`, {\r\n headers: { 'X-API-Key': apiKey },\r\n });\r\n\r\n if (!res.ok) {\r\n if (res.status === 401 || res.status === 403) {\r\n return { success: false, error: 'API key invalid or expired.' };\r\n }\r\n return { success: false, error: `SMSTunnel returned error: ${res.status}` };\r\n }\r\n\r\n const data: any = await res.json();\r\n const devices = data?.devices || [];\r\n const device = devices[0];\r\n const deviceName = device?.name || 'Android Phone';\r\n const deviceId = device?.id || device?._id;\r\n\r\n const update: Record<string, any> = { apiKey, deviceName };\r\n if (deviceId) update.deviceId = deviceId;\r\n await this.options.storage.updateConfig(update);\r\n\r\n this.logger.log(`Paired via API key. Device: ${deviceName}`);\r\n\r\n // Auto-exchange keys\r\n if (deviceId) {\r\n this.exchangeKeys().catch((err) =>\r\n this.logger.warn(`Auto key exchange failed: ${err.message}`),\r\n );\r\n }\r\n\r\n return { success: true, deviceName };\r\n } catch (err: any) {\r\n this.logger.error(`Pair with API key error: ${err.message}`);\r\n return { success: false, error: `Could not connect to SMSTunnel: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Send SMS via SMSTunnel API.\r\n */\r\n async sendSms(to: string, message: string): Promise<SendSmsResult> {\r\n const config = await this.options.storage.getConfig();\r\n\r\n if (!config.apiKey) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel is not configured. Pair a device first.',\r\n };\r\n }\r\n\r\n if (!config.serverUrl) {\r\n return {\r\n success: false,\r\n error: 'SMSTunnel server URL is not configured.',\r\n };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n const res = await fetch(`${serverUrl}/api/v1/sms/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify({ to, message }),\r\n });\r\n\r\n const data: any = await res.json();\r\n\r\n if (data.success) {\r\n this.logger.log(`SMS sent via SMSTunnel: ${data.messageId} -> ${to}`);\r\n return { success: true, messageId: data.messageId };\r\n }\r\n\r\n return {\r\n success: false,\r\n error: data.error || data.message || 'SMSTunnel returned an error',\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`SMS send error: ${err.message}`);\r\n return { success: false, error: `Could not send SMS: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Unpair - clear saved API key and device info.\r\n */\r\n async unpair(): Promise<void> {\r\n await this.options.storage.clearConfig();\r\n this.logger.log('SMSTunnel unpaired');\r\n }\r\n\r\n /**\r\n * Update server URL in config.\r\n */\r\n async updateServerUrl(serverUrl: string): Promise<void> {\r\n await this.options.storage.updateConfig({\r\n serverUrl: serverUrl.replace(/\\/$/, ''),\r\n });\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send 2FA SMS via SMSTunnel API.\r\n */\r\n async send2fa(to: string, code: string, template?: string): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send-2fa', { to, code, template });\r\n }\r\n\r\n /**\r\n * Send bulk SMS via SMSTunnel API.\r\n */\r\n async sendBulk(\r\n messages: Array<{ to: string; message: string }>,\r\n ): Promise<{ success: boolean; results?: any[]; error?: string }> {\r\n return this.apiPost('/api/v1/sms/send-bulk', { messages });\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n */\r\n async getSmsStatus(messageId: string): Promise<any> {\r\n return this.apiGet(`/api/v1/sms/status/${messageId}`);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n */\r\n async getReceivedSms(): Promise<any> {\r\n return this.apiGet('/api/v1/sms/received');\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n */\r\n async getDevices(): Promise<any> {\r\n return this.apiGet('/api/v1/devices');\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n */\r\n async getUsage(): Promise<any> {\r\n return this.apiGet('/api/v1/account/usage');\r\n }\r\n\r\n // ─── E2E Encryption ──────────────────────────────\r\n\r\n /**\r\n * Exchange encryption keys with the paired device.\r\n * Fetches the device's public key from SMSTunnel server and stores it locally.\r\n * Called automatically after pairing, or manually via POST /smstunnel/exchange-keys.\r\n */\r\n async exchangeKeys(): Promise<ExchangeKeysResult> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n\r\n try {\r\n // Resolve deviceId if we don't have it\r\n let deviceId = config.deviceId;\r\n if (!deviceId) {\r\n const listRes = await fetch(`${serverUrl}/api/v1/devices`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n if (!listRes.ok) {\r\n return { success: false, error: `Could not list devices: ${listRes.status}` };\r\n }\r\n const listData: any = await listRes.json();\r\n const devices = listData?.devices || [];\r\n if (devices.length === 0) {\r\n return { success: false, error: 'No paired device found.' };\r\n }\r\n deviceId = devices[0].id || devices[0]._id;\r\n await this.options.storage.updateConfig({ deviceId });\r\n }\r\n\r\n // Fetch device detail (includes publicKey)\r\n const res = await fetch(`${serverUrl}/api/v1/devices/${deviceId}`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n\r\n if (!res.ok) {\r\n return { success: false, error: `Could not fetch device: ${res.status}` };\r\n }\r\n\r\n const device: any = await res.json();\r\n\r\n if (!device.publicKey) {\r\n return { success: false, error: 'Device has no public key. Enable encryption in SMSTunnel app on the phone.' };\r\n }\r\n\r\n await this.options.storage.updateConfig({\r\n deviceId,\r\n devicePublicKey: device.publicKey,\r\n deviceFingerprint: device.publicKeyFingerprint,\r\n deviceName: device.name || config.deviceName,\r\n });\r\n\r\n this.logger.log(`E2E keys exchanged. Fingerprint: ${device.publicKeyFingerprint}`);\r\n return {\r\n success: true,\r\n fingerprint: device.publicKeyFingerprint,\r\n deviceName: device.name,\r\n };\r\n } catch (err: any) {\r\n this.logger.error(`Key exchange error: ${err.message}`);\r\n return { success: false, error: `Key exchange failed: ${err.message}` };\r\n }\r\n }\r\n\r\n /**\r\n * Send encrypted SMS via SMSTunnel API.\r\n * The payload is encrypted client-side with the device's RSA public key.\r\n */\r\n async sendEncryptedSms(\r\n encryptedPayload: string,\r\n deviceId: string,\r\n is2FA: boolean = false,\r\n ): Promise<SendSmsResult> {\r\n return this.apiPost('/api/v1/sms/send', {\r\n encrypted: true,\r\n encryptedPayload,\r\n deviceId,\r\n is2FA,\r\n });\r\n }\r\n\r\n /**\r\n * List pairings for the authenticated user.\r\n */\r\n async getPairings(): Promise<any> {\r\n return this.apiGet('/api/v1/pairings');\r\n }\r\n\r\n // ─── Internal helpers ──────────────────────────────\r\n\r\n /** Make an authenticated GET request to SMSTunnel */\r\n private async apiGet(path: string): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n headers: { 'X-API-Key': config.apiKey },\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n\r\n /** Make an authenticated POST request to SMSTunnel */\r\n private async apiPost(path: string, body: any): Promise<any> {\r\n const config = await this.options.storage.getConfig();\r\n if (!config.apiKey || !config.serverUrl) {\r\n return { success: false, error: 'SMSTunnel is not configured.' };\r\n }\r\n const serverUrl = config.serverUrl.replace(/\\/$/, '');\r\n try {\r\n const res = await fetch(`${serverUrl}${path}`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-API-Key': config.apiKey,\r\n },\r\n body: JSON.stringify(body),\r\n });\r\n return await res.json();\r\n } catch (err: any) {\r\n return { success: false, error: err.message };\r\n }\r\n }\r\n}\r\n","export const SMSTUNNEL_OPTIONS = 'SMSTUNNEL_OPTIONS';\r\n\r\nexport const SMSTUNNEL_DEFAULT_PREFIX = 'smstunnel';\r\n\r\n/**\r\n * Public paths that should be excluded from auth guards.\r\n * Use with your global guard to skip auth on callback/polling routes.\r\n *\r\n * Example (NestJS):\r\n * if (SMSTUNNEL_PUBLIC_PATHS.some(p => request.url.includes(p))) return true;\r\n */\r\nexport const SMSTUNNEL_PUBLIC_PATHS = [\r\n '/smstunnel/pairing-status/',\r\n '/smstunnel/callback',\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n];\r\n\r\n/**\r\n * Returns public paths with a custom prefix.\r\n */\r\nexport function getSmsTunnelPublicPaths(prefix: string): string[] {\r\n return [\r\n `/${prefix}/pairing-status/`,\r\n `/${prefix}/callback`,\r\n '/wp-json/smstunnel/v1/setup-callback',\r\n ];\r\n}\r\n","import {\r\n Controller,\r\n Post,\r\n Get,\r\n Body,\r\n Param,\r\n Inject,\r\n Logger,\r\n} from '@nestjs/common';\r\nimport { SmsTunnelService } from './smstunnel.service';\r\nimport { SMSTUNNEL_OPTIONS } from './smstunnel.constants';\r\nimport type {\r\n SmsTunnelModuleOptions,\r\n PairingCallbackBody,\r\n LegacyCallbackBody,\r\n} from './smstunnel.interfaces';\r\n\r\n/**\r\n * Decorator to mark routes as public (no auth).\r\n * If you have your own @Public() decorator, the module re-exports\r\n * SMSTUNNEL_PUBLIC_PATHS so you can exclude them in your guard.\r\n */\r\nfunction SmsTunnelPublic() {\r\n // We use Reflect metadata so consumers can detect public routes\r\n return (target: any, key: string, descriptor: PropertyDescriptor) => {\r\n Reflect.defineMetadata('isPublic', true, descriptor.value);\r\n return descriptor;\r\n };\r\n}\r\n\r\n@Controller()\r\nexport class SmsTunnelController {\r\n private readonly logger = new Logger(SmsTunnelController.name);\r\n private readonly prefix: string;\r\n\r\n constructor(\r\n private readonly smsTunnelService: SmsTunnelService,\r\n @Inject(SMSTUNNEL_OPTIONS)\r\n private readonly options: SmsTunnelModuleOptions,\r\n ) {\r\n this.prefix = 'smstunnel';\r\n }\r\n\r\n /**\r\n * Get current pairing status.\r\n * Route: GET /{prefix}/status\r\n */\r\n @Get('smstunnel/status')\r\n async getStatus() {\r\n return this.smsTunnelService.getStatus();\r\n }\r\n\r\n /**\r\n * Create a pairing token on SMSTunnel.\r\n * Route: POST /{prefix}/create-token\r\n */\r\n @Post('smstunnel/create-token')\r\n async createToken() {\r\n return this.smsTunnelService.createPairingToken();\r\n }\r\n\r\n /**\r\n * Proxy polling to SMSTunnel (avoids CORS issues).\r\n * This should be public (no auth) - just proxying SMSTunnel's endpoint.\r\n * Route: GET /{prefix}/pairing-status/:token\r\n */\r\n @SmsTunnelPublic()\r\n @Get('smstunnel/pairing-status/:token')\r\n async pairingStatus(@Param('token') token: string) {\r\n return this.smsTunnelService.getPairingStatus(token);\r\n }\r\n\r\n /**\r\n * Callback from SMSTunnel after pairing completes.\r\n * Route: POST /{prefix}/callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('smstunnel/callback')\r\n async pairingCallback(@Body() body: PairingCallbackBody) {\r\n this.logger.log(\r\n `SMSTunnel callback: event=${body.event}, token=${body.token?.substring(0, 8)}...`,\r\n );\r\n const success = await this.smsTunnelService.handlePairingCallback(body);\r\n return { success };\r\n }\r\n\r\n /**\r\n * WordPress-compatible legacy callback.\r\n * Route: POST /wp-json/smstunnel/v1/setup-callback\r\n */\r\n @SmsTunnelPublic()\r\n @Post('wp-json/smstunnel/v1/setup-callback')\r\n async setupCallbackWp(@Body() body: LegacyCallbackBody) {\r\n this.logger.log(`SMSTunnel legacy callback. Status: ${body.status || 'unknown'}`);\r\n const success = await this.smsTunnelService.handleLegacyCallback(\r\n body.site_token,\r\n body.api_key,\r\n body.device_name,\r\n );\r\n return { success };\r\n }\r\n\r\n /**\r\n * Pair using an API key directly (alternative to QR).\r\n * Route: POST /{prefix}/pair-with-key\r\n */\r\n @Post('smstunnel/pair-with-key')\r\n async pairWithKey(@Body() body: { apiKey: string }) {\r\n return this.smsTunnelService.pairWithApiKey(body.apiKey);\r\n }\r\n\r\n /**\r\n * Unpair the connected device.\r\n * Route: POST /{prefix}/unpair\r\n */\r\n @Post('smstunnel/unpair')\r\n async unpair() {\r\n await this.smsTunnelService.unpair();\r\n return { success: true };\r\n }\r\n\r\n /**\r\n * Send an SMS via SMSTunnel.\r\n * Route: POST /{prefix}/send\r\n */\r\n @Post('smstunnel/send')\r\n async sendSms(@Body() body: { to: string; message: string }) {\r\n return this.smsTunnelService.sendSms(body.to, body.message);\r\n }\r\n\r\n /**\r\n * Update server URL configuration.\r\n * Route: POST /{prefix}/update-config\r\n */\r\n @Post('smstunnel/update-config')\r\n async updateConfig(@Body() body: { serverUrl: string }) {\r\n await this.smsTunnelService.updateServerUrl(body.serverUrl);\r\n return { success: true };\r\n }\r\n\r\n // ─── Extended SMS endpoints ────────────────────────\r\n\r\n /**\r\n * Send a 2FA SMS.\r\n * Route: POST /{prefix}/send-2fa\r\n */\r\n @Post('smstunnel/send-2fa')\r\n async send2fa(@Body() body: { to: string; code: string; template?: string }) {\r\n return this.smsTunnelService.send2fa(body.to, body.code, body.template);\r\n }\r\n\r\n /**\r\n * Send bulk SMS.\r\n * Route: POST /{prefix}/send-bulk\r\n */\r\n @Post('smstunnel/send-bulk')\r\n async sendBulk(@Body() body: { messages: Array<{ to: string; message: string }> }) {\r\n return this.smsTunnelService.sendBulk(body.messages);\r\n }\r\n\r\n /**\r\n * Get SMS delivery status.\r\n * Route: GET /{prefix}/sms-status/:messageId\r\n */\r\n @Get('smstunnel/sms-status/:messageId')\r\n async smsStatus(@Param('messageId') messageId: string) {\r\n return this.smsTunnelService.getSmsStatus(messageId);\r\n }\r\n\r\n /**\r\n * Get received SMS (inbox).\r\n * Route: GET /{prefix}/received\r\n */\r\n @Get('smstunnel/received')\r\n async receivedSms() {\r\n return this.smsTunnelService.getReceivedSms();\r\n }\r\n\r\n // ─── E2E Encryption ────────────────────────────────\r\n\r\n /**\r\n * Exchange encryption keys with the paired device.\r\n * Fetches the device's public key from SMSTunnel and stores it locally.\r\n * Route: POST /{prefix}/exchange-keys\r\n */\r\n @Post('smstunnel/exchange-keys')\r\n async exchangeKeys() {\r\n return this.smsTunnelService.exchangeKeys();\r\n }\r\n\r\n /**\r\n * Send encrypted SMS.\r\n * Route: POST /{prefix}/send-encrypted\r\n */\r\n @Post('smstunnel/send-encrypted')\r\n async sendEncrypted(\r\n @Body() body: { encryptedPayload: string; deviceId: string; is2FA?: boolean },\r\n ) {\r\n return this.smsTunnelService.sendEncryptedSms(\r\n body.encryptedPayload,\r\n body.deviceId,\r\n body.is2FA,\r\n );\r\n }\r\n\r\n /**\r\n * List pairings.\r\n * Route: GET /{prefix}/pairings\r\n */\r\n @Get('smstunnel/pairings')\r\n async pairings() {\r\n return this.smsTunnelService.getPairings();\r\n }\r\n\r\n // ─── Devices & Account ─────────────────────────────\r\n\r\n /**\r\n * List paired devices.\r\n * Route: GET /{prefix}/devices\r\n */\r\n @Get('smstunnel/devices')\r\n async devices() {\r\n return this.smsTunnelService.getDevices();\r\n }\r\n\r\n /**\r\n * Get account usage stats.\r\n * Route: GET /{prefix}/usage\r\n */\r\n @Get('smstunnel/usage')\r\n async usage() {\r\n return this.smsTunnelService.getUsage();\r\n }\r\n}\r\n"],"mappings":"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","names":["Inject","Logger","Logger","Inject"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narcisbodea/smstunnel-sdk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"description": "SMSTunnel SDK - Node.js + NestJS + React + Vue + Angular + framework-agnostic client for SMS pairing & E2E encryption",
|
|
5
5
|
"author": "NARBOWEB SRL",
|
|
6
6
|
"license": "MIT",
|