cypress-mailisk 3.0.3 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/package.json +1 -1
- package/src/mailiskCommands.d.ts +227 -0
- package/src/mailiskCommands.js +67 -0
- package/src/request.js +2 -0
package/README.md
CHANGED
|
@@ -212,6 +212,46 @@ cy.mailiskListSmsNumbers().then((response) => {
|
|
|
212
212
|
});
|
|
213
213
|
```
|
|
214
214
|
|
|
215
|
+
### TOTP authenticator devices
|
|
216
|
+
|
|
217
|
+
These commands manage saved Mailisk Authenticator devices and generate OTP codes through the Mailisk API. Use an organisation API key for these endpoints.
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
cy.mailiskDeviceList({ issuer: 'GitHub', username: 'qa@example.com' }).then((response) => {
|
|
221
|
+
const devices = response.items;
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
cy.mailiskDeviceCreate({
|
|
225
|
+
name: 'GitHub staging',
|
|
226
|
+
sharedSecret: 'JBSWY3DPEHPK3PXP',
|
|
227
|
+
}).then((device) => {
|
|
228
|
+
cy.mailiskDeviceOtpByDeviceId(device.id).then((otp) => {
|
|
229
|
+
expect(otp.code).to.match(/^\d{6}$/);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
cy.mailiskDeviceCreateCustom({
|
|
234
|
+
secret: 'JBSWY3DPEHPK3PXP',
|
|
235
|
+
username: 'qa@example.com',
|
|
236
|
+
issuer: 'GitHub',
|
|
237
|
+
digits: 6,
|
|
238
|
+
period: 30,
|
|
239
|
+
algorithm: 'SHA1',
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
cy.mailiskDeviceCreateFromBase32SecretKey({
|
|
243
|
+
base32SecretKey: 'JBSWY3DPEHPK3PXP',
|
|
244
|
+
issuer: 'GitHub',
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
cy.mailiskDeviceCreateFromOtpAuthUrl({
|
|
248
|
+
otpAuthUrl: 'otpauth://totp/GitHub:qa@example.com?secret=JBSWY3DPEHPK3PXP&issuer=GitHub',
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
cy.mailiskDeviceOtpBySharedSecret('JBSWY3DPEHPK3PXP'); // one-off code without saving a device
|
|
252
|
+
cy.mailiskDeviceDelete('9b1f6ec0-b90d-4bd8-8dd0-f6b2d5138273');
|
|
253
|
+
```
|
|
254
|
+
|
|
215
255
|
## Common test cases
|
|
216
256
|
|
|
217
257
|
### Working with email attachments
|
package/package.json
CHANGED
package/src/mailiskCommands.d.ts
CHANGED
|
@@ -219,6 +219,129 @@ export interface SendVirtualSmsParams {
|
|
|
219
219
|
body: string;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
export type TotpAlgorithm = 'SHA1' | 'SHA256' | 'SHA512';
|
|
223
|
+
|
|
224
|
+
export type KnownTotpDeviceSource = 'shared_secret' | 'custom' | 'base32_secret_key' | 'otpauth_url';
|
|
225
|
+
|
|
226
|
+
export type TotpDeviceSource = KnownTotpDeviceSource | (string & {});
|
|
227
|
+
|
|
228
|
+
export interface TotpDevice {
|
|
229
|
+
/** Unique identifier for the saved authenticator device */
|
|
230
|
+
id: string;
|
|
231
|
+
/** Unique identifier for the organisation */
|
|
232
|
+
organisation_id: string;
|
|
233
|
+
/** Device display name */
|
|
234
|
+
name: string;
|
|
235
|
+
/** Account label, if one is specified */
|
|
236
|
+
username?: string | null;
|
|
237
|
+
/** Issuer/app label, if one is specified */
|
|
238
|
+
issuer?: string | null;
|
|
239
|
+
/** Number of digits in generated OTP codes */
|
|
240
|
+
digits: number;
|
|
241
|
+
/** OTP validity period in seconds */
|
|
242
|
+
period: number;
|
|
243
|
+
/** TOTP hash algorithm */
|
|
244
|
+
algorithm: TotpAlgorithm;
|
|
245
|
+
/** Source used to create the saved device */
|
|
246
|
+
source: TotpDeviceSource;
|
|
247
|
+
/** Optional expiration timestamp */
|
|
248
|
+
expiresAt?: string | null;
|
|
249
|
+
/** Date and time the device was created */
|
|
250
|
+
created_at: string;
|
|
251
|
+
/** Date and time the device was updated */
|
|
252
|
+
updated_at: string;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface TotpDeviceListParams {
|
|
256
|
+
/** Maximum number of devices returned. */
|
|
257
|
+
limit?: number;
|
|
258
|
+
/** Number of devices to skip. */
|
|
259
|
+
offset?: number;
|
|
260
|
+
/** Case-insensitive partial username match. */
|
|
261
|
+
username?: string;
|
|
262
|
+
/** Case-insensitive partial issuer match. */
|
|
263
|
+
issuer?: string;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export interface TotpDeviceListResponse {
|
|
267
|
+
total_count: number;
|
|
268
|
+
options: TotpDeviceListParams;
|
|
269
|
+
items: TotpDevice[];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface CreateTotpDeviceParams {
|
|
273
|
+
/** Base32 shared secret. */
|
|
274
|
+
sharedSecret: string;
|
|
275
|
+
/** Optional device display name. */
|
|
276
|
+
name?: string;
|
|
277
|
+
/** Optional future ISO expiration timestamp. */
|
|
278
|
+
expiresAt?: string;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export interface CreateCustomTotpDeviceParams {
|
|
282
|
+
/** Base32 shared secret. */
|
|
283
|
+
secret: string;
|
|
284
|
+
/** Optional device display name. */
|
|
285
|
+
name?: string;
|
|
286
|
+
/** Optional account label. */
|
|
287
|
+
username?: string;
|
|
288
|
+
/** Optional issuer/app label. */
|
|
289
|
+
issuer?: string;
|
|
290
|
+
/** Number of digits in generated OTP codes. */
|
|
291
|
+
digits?: 6 | 8;
|
|
292
|
+
/** OTP validity period in seconds. */
|
|
293
|
+
period?: number;
|
|
294
|
+
/** TOTP hash algorithm. */
|
|
295
|
+
algorithm?: TotpAlgorithm;
|
|
296
|
+
/** Optional future ISO expiration timestamp. */
|
|
297
|
+
expiresAt?: string;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export interface CreateBase32SecretKeyTotpDeviceParams {
|
|
301
|
+
/** Base32 secret key. */
|
|
302
|
+
base32SecretKey: string;
|
|
303
|
+
/** Optional device display name. */
|
|
304
|
+
name?: string;
|
|
305
|
+
/** Optional account label. */
|
|
306
|
+
username?: string;
|
|
307
|
+
/** Optional issuer/app label. */
|
|
308
|
+
issuer?: string;
|
|
309
|
+
/** Number of digits in generated OTP codes. */
|
|
310
|
+
digits?: 6 | 8;
|
|
311
|
+
/** OTP validity period in seconds. */
|
|
312
|
+
period?: number;
|
|
313
|
+
/** TOTP hash algorithm. */
|
|
314
|
+
algorithm?: TotpAlgorithm;
|
|
315
|
+
/** Optional future ISO expiration timestamp. */
|
|
316
|
+
expiresAt?: string;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export interface CreateOtpAuthUrlTotpDeviceParams {
|
|
320
|
+
/** otpauth://totp URL with a secret query parameter. */
|
|
321
|
+
otpAuthUrl: string;
|
|
322
|
+
/** Optional device display name override. */
|
|
323
|
+
name?: string;
|
|
324
|
+
/** Optional account label fallback. */
|
|
325
|
+
username?: string;
|
|
326
|
+
/** Optional issuer/app label fallback. */
|
|
327
|
+
issuer?: string;
|
|
328
|
+
/** Number of digits in generated OTP codes, used only if missing from the URL. */
|
|
329
|
+
digits?: 6 | 8;
|
|
330
|
+
/** OTP validity period in seconds, used only if missing from the URL. */
|
|
331
|
+
period?: number;
|
|
332
|
+
/** TOTP hash algorithm, used only if missing from the URL. */
|
|
333
|
+
algorithm?: TotpAlgorithm;
|
|
334
|
+
/** Optional future ISO expiration timestamp. */
|
|
335
|
+
expiresAt?: string;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export interface TotpOtpResponse {
|
|
339
|
+
/** Generated one-time password code. */
|
|
340
|
+
code: string;
|
|
341
|
+
/** ISO timestamp when the code expires. */
|
|
342
|
+
expires: string;
|
|
343
|
+
}
|
|
344
|
+
|
|
222
345
|
declare global {
|
|
223
346
|
namespace Cypress {
|
|
224
347
|
interface Chainable {
|
|
@@ -292,6 +415,110 @@ declare global {
|
|
|
292
415
|
*/
|
|
293
416
|
options?: Partial<Cypress.RequestOptions>,
|
|
294
417
|
): Cypress.Chainable<ListSmsNumbersResponse>;
|
|
418
|
+
|
|
419
|
+
mailiskDeviceList(
|
|
420
|
+
/**
|
|
421
|
+
* List filters and pagination options.
|
|
422
|
+
*/
|
|
423
|
+
params?: TotpDeviceListParams,
|
|
424
|
+
/**
|
|
425
|
+
* Request options.
|
|
426
|
+
*
|
|
427
|
+
* See https://docs.cypress.io/api/commands/request#Arguments
|
|
428
|
+
*/
|
|
429
|
+
options?: Partial<Cypress.RequestOptions>,
|
|
430
|
+
): Cypress.Chainable<TotpDeviceListResponse>;
|
|
431
|
+
|
|
432
|
+
mailiskDeviceCreate(
|
|
433
|
+
/**
|
|
434
|
+
* Saved device input using default TOTP settings.
|
|
435
|
+
*/
|
|
436
|
+
input: CreateTotpDeviceParams,
|
|
437
|
+
/**
|
|
438
|
+
* Request options.
|
|
439
|
+
*
|
|
440
|
+
* See https://docs.cypress.io/api/commands/request#Arguments
|
|
441
|
+
*/
|
|
442
|
+
options?: Partial<Cypress.RequestOptions>,
|
|
443
|
+
): Cypress.Chainable<TotpDevice>;
|
|
444
|
+
|
|
445
|
+
mailiskDeviceCreateCustom(
|
|
446
|
+
/**
|
|
447
|
+
* Saved device input using custom TOTP settings.
|
|
448
|
+
*/
|
|
449
|
+
input: CreateCustomTotpDeviceParams,
|
|
450
|
+
/**
|
|
451
|
+
* Request options.
|
|
452
|
+
*
|
|
453
|
+
* See https://docs.cypress.io/api/commands/request#Arguments
|
|
454
|
+
*/
|
|
455
|
+
options?: Partial<Cypress.RequestOptions>,
|
|
456
|
+
): Cypress.Chainable<TotpDevice>;
|
|
457
|
+
|
|
458
|
+
mailiskDeviceCreateFromBase32SecretKey(
|
|
459
|
+
/**
|
|
460
|
+
* Saved device input using a Base32 secret key.
|
|
461
|
+
*/
|
|
462
|
+
input: CreateBase32SecretKeyTotpDeviceParams,
|
|
463
|
+
/**
|
|
464
|
+
* Request options.
|
|
465
|
+
*
|
|
466
|
+
* See https://docs.cypress.io/api/commands/request#Arguments
|
|
467
|
+
*/
|
|
468
|
+
options?: Partial<Cypress.RequestOptions>,
|
|
469
|
+
): Cypress.Chainable<TotpDevice>;
|
|
470
|
+
|
|
471
|
+
mailiskDeviceCreateFromOtpAuthUrl(
|
|
472
|
+
/**
|
|
473
|
+
* Saved device input using an otpauth URL.
|
|
474
|
+
*/
|
|
475
|
+
input: CreateOtpAuthUrlTotpDeviceParams,
|
|
476
|
+
/**
|
|
477
|
+
* Request options.
|
|
478
|
+
*
|
|
479
|
+
* See https://docs.cypress.io/api/commands/request#Arguments
|
|
480
|
+
*/
|
|
481
|
+
options?: Partial<Cypress.RequestOptions>,
|
|
482
|
+
): Cypress.Chainable<TotpDevice>;
|
|
483
|
+
|
|
484
|
+
mailiskDeviceOtpByDeviceId(
|
|
485
|
+
/**
|
|
486
|
+
* Saved device ID.
|
|
487
|
+
*/
|
|
488
|
+
deviceId: string,
|
|
489
|
+
/**
|
|
490
|
+
* Request options.
|
|
491
|
+
*
|
|
492
|
+
* See https://docs.cypress.io/api/commands/request#Arguments
|
|
493
|
+
*/
|
|
494
|
+
options?: Partial<Cypress.RequestOptions>,
|
|
495
|
+
): Cypress.Chainable<TotpOtpResponse>;
|
|
496
|
+
|
|
497
|
+
mailiskDeviceOtpBySharedSecret(
|
|
498
|
+
/**
|
|
499
|
+
* Shared secret for one-off OTP generation.
|
|
500
|
+
*/
|
|
501
|
+
sharedSecret: string,
|
|
502
|
+
/**
|
|
503
|
+
* Request options.
|
|
504
|
+
*
|
|
505
|
+
* See https://docs.cypress.io/api/commands/request#Arguments
|
|
506
|
+
*/
|
|
507
|
+
options?: Partial<Cypress.RequestOptions>,
|
|
508
|
+
): Cypress.Chainable<TotpOtpResponse>;
|
|
509
|
+
|
|
510
|
+
mailiskDeviceDelete(
|
|
511
|
+
/**
|
|
512
|
+
* Saved device ID.
|
|
513
|
+
*/
|
|
514
|
+
deviceId: string,
|
|
515
|
+
/**
|
|
516
|
+
* Request options.
|
|
517
|
+
*
|
|
518
|
+
* See https://docs.cypress.io/api/commands/request#Arguments
|
|
519
|
+
*/
|
|
520
|
+
options?: Partial<Cypress.RequestOptions>,
|
|
521
|
+
): Cypress.Chainable<void>;
|
|
295
522
|
}
|
|
296
523
|
}
|
|
297
524
|
}
|
package/src/mailiskCommands.js
CHANGED
|
@@ -10,6 +10,14 @@ class MailiskCommands {
|
|
|
10
10
|
'mailiskDownloadAttachment',
|
|
11
11
|
'mailiskSearchSms',
|
|
12
12
|
'mailiskListSmsNumbers',
|
|
13
|
+
'mailiskDeviceList',
|
|
14
|
+
'mailiskDeviceCreate',
|
|
15
|
+
'mailiskDeviceCreateCustom',
|
|
16
|
+
'mailiskDeviceCreateFromBase32SecretKey',
|
|
17
|
+
'mailiskDeviceCreateFromOtpAuthUrl',
|
|
18
|
+
'mailiskDeviceOtpByDeviceId',
|
|
19
|
+
'mailiskDeviceOtpBySharedSecret',
|
|
20
|
+
'mailiskDeviceDelete',
|
|
13
21
|
];
|
|
14
22
|
}
|
|
15
23
|
|
|
@@ -186,6 +194,65 @@ class MailiskCommands {
|
|
|
186
194
|
mailiskListSmsNumbers(options = {}) {
|
|
187
195
|
return this._withRequest((request) => request.get(`api/sms/numbers`, options));
|
|
188
196
|
}
|
|
197
|
+
|
|
198
|
+
_buildUrlParams(params = {}) {
|
|
199
|
+
const urlParams = new URLSearchParams();
|
|
200
|
+
for (const key in params) {
|
|
201
|
+
let value = params[key];
|
|
202
|
+
if (typeof value === 'string') {
|
|
203
|
+
value = value.trim();
|
|
204
|
+
}
|
|
205
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
206
|
+
urlParams.set(key, value.toString());
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return urlParams;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
_requireNonEmptyString(value, label) {
|
|
213
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
214
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
215
|
+
}
|
|
216
|
+
return value.trim();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
mailiskDeviceList(params = {}, options = {}) {
|
|
220
|
+
const urlParams = this._buildUrlParams(params);
|
|
221
|
+
const query = urlParams.toString();
|
|
222
|
+
const path = query ? `api/devices?${query}` : 'api/devices';
|
|
223
|
+
return this._withRequest((request) => request.get(path, options));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
mailiskDeviceCreate(input, options = {}) {
|
|
227
|
+
return this._withRequest((request) => request.post('api/devices', input, options));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
mailiskDeviceCreateCustom(input, options = {}) {
|
|
231
|
+
return this._withRequest((request) => request.post('api/devices/custom', input, options));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
mailiskDeviceCreateFromBase32SecretKey(input, options = {}) {
|
|
235
|
+
return this._withRequest((request) => request.post('api/devices/base32-secret-key', input, options));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
mailiskDeviceCreateFromOtpAuthUrl(input, options = {}) {
|
|
239
|
+
return this._withRequest((request) => request.post('api/devices/otpauth-url', input, options));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
mailiskDeviceOtpByDeviceId(deviceId, options = {}) {
|
|
243
|
+
const encodedDeviceId = encodeURIComponent(this._requireNonEmptyString(deviceId, 'deviceId'));
|
|
244
|
+
return this._withRequest((request) => request.get(`api/devices/${encodedDeviceId}/otp`, options));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
mailiskDeviceOtpBySharedSecret(sharedSecret, options = {}) {
|
|
248
|
+
const normalizedSharedSecret = this._requireNonEmptyString(sharedSecret, 'sharedSecret');
|
|
249
|
+
return this._withRequest((request) => request.post('api/devices/otp', { sharedSecret: normalizedSharedSecret }, options));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
mailiskDeviceDelete(deviceId, options = {}) {
|
|
253
|
+
const encodedDeviceId = encodeURIComponent(this._requireNonEmptyString(deviceId, 'deviceId'));
|
|
254
|
+
return this._withRequest((request) => request.del(`api/devices/${encodedDeviceId}`, options).then(() => undefined));
|
|
255
|
+
}
|
|
189
256
|
}
|
|
190
257
|
|
|
191
258
|
module.exports = MailiskCommands;
|
package/src/request.js
CHANGED
|
@@ -6,6 +6,7 @@ class Request {
|
|
|
6
6
|
this.apiKey = options.apiKey;
|
|
7
7
|
this.headers = {
|
|
8
8
|
Accept: 'application/json',
|
|
9
|
+
'Content-Type': 'application/json',
|
|
9
10
|
'X-Api-Key': `${this.apiKey}`,
|
|
10
11
|
'User-Agent': `cypress-mailisk/${pkg.version}`,
|
|
11
12
|
};
|
|
@@ -22,6 +23,7 @@ class Request {
|
|
|
22
23
|
url: `${this.apiUrl}${path}`,
|
|
23
24
|
headers: {
|
|
24
25
|
Accept: this.headers.Accept,
|
|
26
|
+
'Content-Type': this.headers['Content-Type'],
|
|
25
27
|
'X-Api-Key': this.headers['X-Api-Key'],
|
|
26
28
|
'User-Agent': this.headers['User-Agent'],
|
|
27
29
|
},
|