@auth0/auth0-checkmate 1.4.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.
Files changed (114) hide show
  1. package/.github/CODEOWNERS +1 -0
  2. package/.github/workflows/npm-release.yml +77 -0
  3. package/.github/workflows/sca_scan.yml +10 -0
  4. package/.github/workflows/test.yml +48 -0
  5. package/AUTHORS +5 -0
  6. package/LICENSE +203 -0
  7. package/README.md +166 -0
  8. package/THIRD-PARTY-NOTICES +226 -0
  9. package/analyzer/lib/actions/checkActionsHardCodedValues.js +151 -0
  10. package/analyzer/lib/actions/checkActionsRuntime.js +105 -0
  11. package/analyzer/lib/actions/checkDependencies.js +111 -0
  12. package/analyzer/lib/attack_protection/checkBotDetectionSetting.js +76 -0
  13. package/analyzer/lib/attack_protection/checkBreachedPassword.js +140 -0
  14. package/analyzer/lib/attack_protection/checkBruteForce.js +89 -0
  15. package/analyzer/lib/attack_protection/checkSuspiciousIPThrottling.js +89 -0
  16. package/analyzer/lib/canonical_domain/checkCanonicalDomain.js +63 -0
  17. package/analyzer/lib/clients/checkAllowedCallbacks.js +122 -0
  18. package/analyzer/lib/clients/checkAllowedLogoutUrl.js +124 -0
  19. package/analyzer/lib/clients/checkApplicationLoginUri.js +125 -0
  20. package/analyzer/lib/clients/checkCrossOriginAuthentication.js +91 -0
  21. package/analyzer/lib/clients/checkGrantTypes.js +138 -0
  22. package/analyzer/lib/clients/checkJWTSignAlg.js +118 -0
  23. package/analyzer/lib/clients/checkRefreshToken.js +108 -0
  24. package/analyzer/lib/clients/checkWebOrigins.js +55 -0
  25. package/analyzer/lib/constants.js +63 -0
  26. package/analyzer/lib/custom_domain/checkCustomDomain.js +53 -0
  27. package/analyzer/lib/databases/checkAuthenticationMethods.js +98 -0
  28. package/analyzer/lib/databases/checkDASHardCodedValues.js +163 -0
  29. package/analyzer/lib/databases/checkEmailAttributeVerification.js +114 -0
  30. package/analyzer/lib/databases/checkEnabledDatabaseCustomization.js +83 -0
  31. package/analyzer/lib/databases/checkPasswordComplexity.js +100 -0
  32. package/analyzer/lib/databases/checkPasswordHistory.js +92 -0
  33. package/analyzer/lib/databases/checkPasswordNoPersonalInfo.js +91 -0
  34. package/analyzer/lib/databases/checkPasswordPolicy.js +95 -0
  35. package/analyzer/lib/databases/checkPromotedDBConnection.js +96 -0
  36. package/analyzer/lib/email_provider/checkEmailProvider.js +37 -0
  37. package/analyzer/lib/email_templates/checkEmailTemplates.js +71 -0
  38. package/analyzer/lib/error_page_template/checkErrorPageTemplate.js +153 -0
  39. package/analyzer/lib/event_streams/checkEventStreams.js +71 -0
  40. package/analyzer/lib/executeCheck.js +12 -0
  41. package/analyzer/lib/hooks/checkHooks.js +43 -0
  42. package/analyzer/lib/listOfAnalyser.js +24 -0
  43. package/analyzer/lib/log_streams/checkLogStream.js +60 -0
  44. package/analyzer/lib/logger.js +16 -0
  45. package/analyzer/lib/multifactor/checkGuardianFactors.js +72 -0
  46. package/analyzer/lib/multifactor/checkGuardianPolicy.js +40 -0
  47. package/analyzer/lib/network_acl/checkNetworkACL.js +35 -0
  48. package/analyzer/lib/rules/checkRules.js +102 -0
  49. package/analyzer/lib/tenant_settings/checkDefaultAudience.js +53 -0
  50. package/analyzer/lib/tenant_settings/checkDefaultDirectory.js +48 -0
  51. package/analyzer/lib/tenant_settings/checkEnabledDynamicClientRegistration.js +60 -0
  52. package/analyzer/lib/tenant_settings/checkSandboxVersion.js +37 -0
  53. package/analyzer/lib/tenant_settings/checkSessionLifetime.js +95 -0
  54. package/analyzer/lib/tenant_settings/checkSupportEmail.js +61 -0
  55. package/analyzer/lib/tenant_settings/checkSupportUrl.js +61 -0
  56. package/analyzer/lib/tenant_settings/checkTenantLoginUrl.js +71 -0
  57. package/analyzer/lib/tenant_settings/checkTenantLogoutUrl.js +60 -0
  58. package/analyzer/report.js +404 -0
  59. package/analyzer/tools/auth0.js +443 -0
  60. package/analyzer/tools/helpers.js +71 -0
  61. package/analyzer/tools/summary.js +84 -0
  62. package/analyzer/tools/utils.js +72 -0
  63. package/bin/index.js +393 -0
  64. package/eslint.config.mjs +16 -0
  65. package/images/auth0.png +0 -0
  66. package/images/okta.png +0 -0
  67. package/locales/en.json +1417 -0
  68. package/package.json +66 -0
  69. package/tests/actions/checkActionsHardCodedValues.test.js +106 -0
  70. package/tests/actions/checkActionsRuntime.test.js +102 -0
  71. package/tests/actions/checkDependencies.test.js +131 -0
  72. package/tests/attack_protection/checkBreachedPassword.test.js +253 -0
  73. package/tests/attack_protection/checkBruteForce.test.js +181 -0
  74. package/tests/attack_protection/checkSuspiciousIPThrottling.test.js +222 -0
  75. package/tests/canonical_domain/checkCanonicalDomain.test.js +94 -0
  76. package/tests/clients/checkAllowedCallbacks.test.js +149 -0
  77. package/tests/clients/checkAllowedLogoutUrl.test.js +149 -0
  78. package/tests/clients/checkApplicationLoginUri.test.js +180 -0
  79. package/tests/clients/checkCrossOriginAuthentication.test.js +99 -0
  80. package/tests/clients/checkGrantTypes.test.js +154 -0
  81. package/tests/clients/checkJWTSignAlg.test.js +121 -0
  82. package/tests/clients/checkRefreshToken.test.js +63 -0
  83. package/tests/clients/checkWebOrigins.test.js +140 -0
  84. package/tests/custom_domain/checkCustomDomain.test.js +73 -0
  85. package/tests/databases/checkAuthenticationMethods.test.js +124 -0
  86. package/tests/databases/checkDASHardCodedValues.test.js +77 -0
  87. package/tests/databases/checkEmailAttributeVerification.test.js +79 -0
  88. package/tests/databases/checkEnabledDatabaseCustomization.test.js +68 -0
  89. package/tests/databases/checkPasswordComplexity.test.js +127 -0
  90. package/tests/databases/checkPasswordHistory.test.js +100 -0
  91. package/tests/databases/checkPasswordNoPersonalInfo.test.js +94 -0
  92. package/tests/databases/checkPasswordPolicy.test.js +161 -0
  93. package/tests/databases/checkPromotedDBConnection.test.js +62 -0
  94. package/tests/email_provider/checkEmailProvider.test.js +58 -0
  95. package/tests/email_templates/checkEmailTemplates.test.js +120 -0
  96. package/tests/error_page_template/checkErrorPageTemplate.test.js +315 -0
  97. package/tests/event_streams/checkEventStreams.test.js +118 -0
  98. package/tests/hooks/checkHooks.test.js +112 -0
  99. package/tests/log_streams/checkLogStream.test.js +140 -0
  100. package/tests/multifactor/checkGuardianFactors.test.js +94 -0
  101. package/tests/multifactor/checkGuardianPolicy.test.js +49 -0
  102. package/tests/rules/checkRules.test.js +102 -0
  103. package/tests/tenant_settings/checkDefaultAudience.test.js +62 -0
  104. package/tests/tenant_settings/checkDefaultDirectory.test.js +62 -0
  105. package/tests/tenant_settings/checkEnabledDynamicClientRegistration.test.js +97 -0
  106. package/tests/tenant_settings/checkSandboxVersion.test.js +50 -0
  107. package/tests/tenant_settings/checkSessionLifetime.test.js +108 -0
  108. package/tests/tenant_settings/checkSupportEmail.test.js +77 -0
  109. package/tests/tenant_settings/checkSupportUrl.test.js +77 -0
  110. package/tests/tenant_settings/checkTenantLoginUri.test.js +82 -0
  111. package/tests/tenant_settings/checkTenantLogoutUrl.test.js +108 -0
  112. package/tests/tools/auth0.test.js +833 -0
  113. package/tests/tools/helpers.test.js +692 -0
  114. package/views/pdf_cli_report.handlebars +571 -0
@@ -0,0 +1,833 @@
1
+ const { expect } = require("chai");
2
+ const axios = require("axios");
3
+ const {
4
+ getAccessToken,
5
+ getCustomDomains,
6
+ getApplications,
7
+ getConnections,
8
+ getEmailProvider,
9
+ getEmailTemplates,
10
+ getErrorPageTemplate,
11
+ getBruteForceProtectionSetting,
12
+ getSuspiciousIpSetting,
13
+ getBreachedPasswordSetting,
14
+ getLogStreams,
15
+ getAttackProtection,
16
+ getTenantSettings,
17
+ getGuardianFactors,
18
+ getGuardianPolicies,
19
+ getBotDetectionSetting,
20
+ getRules,
21
+ getHooks,
22
+ getActions,
23
+ getLogs,
24
+ getNetworkACL,
25
+ getEventStreams
26
+ } = require("../../analyzer/tools/auth0");
27
+ const logger = require("../../analyzer/lib/logger");
28
+ const constants = require("../../analyzer/lib/constants");
29
+
30
+ describe("auth0.js", function() {
31
+ let originalAxiosPost;
32
+ let originalAxiosGet;
33
+ let originalLoggerLog;
34
+ let originalConsoleError;
35
+ let originalProcessExit;
36
+
37
+ beforeEach(function() {
38
+ // Store original functions
39
+ originalAxiosPost = axios.post;
40
+ originalAxiosGet = axios.get;
41
+ originalLoggerLog = logger.log;
42
+ originalConsoleError = console.error;
43
+ originalProcessExit = process.exit;
44
+
45
+ // Mock process.exit to prevent test termination
46
+ process.exit = function(code) {
47
+ throw new Error(`process.exit called with code ${code}`);
48
+ };
49
+
50
+ // Mock console.error to capture error messages
51
+ console.error = function() {};
52
+
53
+ // Mock logger.log to capture log messages
54
+ logger.log = function() {};
55
+ });
56
+
57
+ afterEach(function() {
58
+ // Restore original functions
59
+ axios.post = originalAxiosPost;
60
+ axios.get = originalAxiosGet;
61
+ logger.log = originalLoggerLog;
62
+ console.error = originalConsoleError;
63
+ process.exit = originalProcessExit;
64
+ });
65
+
66
+ describe("getAccessToken", function() {
67
+ it("should return existing access token when provided", async function() {
68
+ const existingToken = "existing-token-123";
69
+
70
+ const result = await getAccessToken("test-domain", "client-id", "client-secret", existingToken);
71
+
72
+ expect(result).to.equal(existingToken);
73
+ });
74
+
75
+ it("should return access token on successful authentication", async function() {
76
+ const mockResponse = {
77
+ data: {
78
+ access_token: "test-access-token",
79
+ token_type: "Bearer",
80
+ expires_in: 3600
81
+ }
82
+ };
83
+
84
+ axios.post = async function(url, body) {
85
+ expect(url).to.equal("https://test-domain/oauth/token");
86
+ expect(body.grant_type).to.equal("client_credentials");
87
+ expect(body.client_id).to.equal("test-client-id");
88
+ expect(body.client_secret).to.equal("test-client-secret");
89
+ expect(body.audience).to.equal("https://test-domain/api/v2/");
90
+ return mockResponse;
91
+ };
92
+
93
+ const result = await getAccessToken("test-domain", "test-client-id", "test-client-secret");
94
+
95
+ expect(result).to.equal("test-access-token");
96
+ });
97
+
98
+ it("should exit process on authentication failure", async function() {
99
+ axios.post = async function() {
100
+ throw new Error("Authentication failed");
101
+ };
102
+
103
+ try {
104
+ await getAccessToken("test-domain", "test-client-id", "wrong-secret");
105
+ expect.fail("Should have thrown an error");
106
+ } catch (error) {
107
+ expect(error.message).to.contain("process.exit called with code 1");
108
+ }
109
+ });
110
+ });
111
+
112
+ describe("getCustomDomains", function() {
113
+ it("should return custom domains data on success", async function() {
114
+ const mockDomains = [
115
+ {
116
+ domain_id: "cd_123",
117
+ domain: "auth.example.com",
118
+ status: "ready",
119
+ type: "auth0_managed_certs"
120
+ }
121
+ ];
122
+
123
+ axios.get = async function(url, options) {
124
+ expect(url).to.equal("https://test-domain/api/v2/custom-domains");
125
+ expect(options.headers.Authorization).to.equal("Bearer test-token");
126
+ return { data: mockDomains };
127
+ };
128
+
129
+ const result = await getCustomDomains("test-domain", "test-token");
130
+
131
+ expect(result).to.deep.equal(mockDomains);
132
+ });
133
+
134
+ it("should return empty array on API error", async function() {
135
+ axios.get = async function() {
136
+ throw new Error("API Error");
137
+ };
138
+
139
+ const result = await getCustomDomains("test-domain", "invalid-token");
140
+
141
+ expect(result).to.deep.equal([]);
142
+ });
143
+ });
144
+
145
+ describe("getApplications", function() {
146
+ it("should return all applications with pagination", async function() {
147
+ const mockClients = [
148
+ { client_id: "app1", name: "Test App 1" },
149
+ { client_id: "app2", name: "Test App 2" }
150
+ ];
151
+
152
+ let callCount = 0;
153
+ axios.get = async function(url, options) {
154
+ expect(url).to.equal("https://test-domain/api/v2/clients");
155
+ expect(options.headers.Authorization).to.equal("Bearer test-token");
156
+
157
+ callCount++;
158
+ if (callCount === 1) {
159
+ // First page with full results
160
+ return { data: new Array(100).fill().map((_, i) => ({ client_id: `app${i}`, name: `App ${i}` })) };
161
+ } else {
162
+ // Second page with partial results
163
+ return { data: mockClients };
164
+ }
165
+ };
166
+
167
+ const result = await getApplications("test-domain", "test-token");
168
+
169
+ expect(result.length).to.equal(102); // 100 from first page + 2 from second page
170
+ });
171
+
172
+ it("should return empty array on error", async function() {
173
+ axios.get = async function() {
174
+ throw new Error("Network error");
175
+ };
176
+
177
+ const result = await getApplications("test-domain", "test-token");
178
+
179
+ expect(result).to.deep.equal([]);
180
+ });
181
+ });
182
+
183
+ describe("getConnections", function() {
184
+ it("should return database connections on success", async function() {
185
+ const mockConnections = [
186
+ {
187
+ id: "con_123",
188
+ name: "Username-Password-Authentication",
189
+ strategy: "auth0",
190
+ enabled_clients: ["client_123"]
191
+ }
192
+ ];
193
+
194
+ axios.get = async function(url, options) {
195
+ expect(url).to.equal("https://test-domain/api/v2/connections?strategy=auth0");
196
+ expect(options.headers.Authorization).to.equal("Bearer test-token");
197
+ return { data: mockConnections };
198
+ };
199
+
200
+ const result = await getConnections("test-domain", "test-token");
201
+
202
+ expect(result).to.deep.equal(mockConnections);
203
+ });
204
+
205
+ it("should return empty array on error", async function() {
206
+ axios.get = async function() {
207
+ throw new Error("Connection error");
208
+ };
209
+
210
+ const result = await getConnections("test-domain", "test-token");
211
+
212
+ expect(result).to.deep.equal([]);
213
+ });
214
+ });
215
+
216
+ describe("getEmailProvider", function() {
217
+ it("should return email provider configuration on success", async function() {
218
+ const mockProvider = {
219
+ name: "sendgrid",
220
+ enabled: true,
221
+ default_from_address: "noreply@example.com",
222
+ credentials: { api_key: "sg.***" }
223
+ };
224
+
225
+ axios.get = async function(url, options) {
226
+ expect(url).to.equal("https://test-domain/api/v2/emails/provider");
227
+ expect(options.headers.Authorization).to.equal("Bearer test-token");
228
+ return { data: mockProvider };
229
+ };
230
+
231
+ const result = await getEmailProvider("test-domain", "test-token");
232
+
233
+ expect(result).to.deep.equal(mockProvider);
234
+ });
235
+
236
+ it("should return null on error", async function() {
237
+ axios.get = async function() {
238
+ throw new Error("Provider not configured");
239
+ };
240
+
241
+ const result = await getEmailProvider("test-domain", "test-token");
242
+
243
+ expect(result).to.be.null;
244
+ });
245
+ });
246
+
247
+ describe("getEmailTemplates", function() {
248
+ it("should return all email templates", async function() {
249
+ let callCount = 0;
250
+ const mockTemplates = {
251
+ verify_email: { enabled: true, template: "Verify: {{user.email}}" },
252
+ reset_email: { enabled: true, template: "Reset: {{user.email}}" }
253
+ };
254
+
255
+ axios.get = async function(url, options) {
256
+ expect(url).to.match(/https:\/\/test-domain\/api\/v2\/email-templates\/.+/);
257
+ expect(options.headers.Authorization).to.equal("Bearer test-token");
258
+
259
+ const templateName = url.split('/').pop();
260
+ callCount++;
261
+
262
+ if (mockTemplates[templateName]) {
263
+ return { data: mockTemplates[templateName] };
264
+ } else {
265
+ return { data: null };
266
+ }
267
+ };
268
+
269
+ const result = await getEmailTemplates("test-domain", "test-token");
270
+
271
+ expect(result).to.be.an("array");
272
+ expect(result.length).to.equal(constants.EMAIL_TEMPLATES_TYPES.length);
273
+ expect(callCount).to.equal(constants.EMAIL_TEMPLATES_TYPES.length);
274
+ });
275
+ });
276
+
277
+ describe("getErrorPageTemplate", function() {
278
+ it("should return error page template on success", async function() {
279
+ const mockResponse = {
280
+ error_page: {
281
+ html: "<html>Error: {{error}}</html>"
282
+ }
283
+ };
284
+
285
+ axios.get = async function(url, options) {
286
+ expect(url).to.equal("https://test-domain/api/v2/tenants/settings");
287
+ expect(options.headers.Authorization).to.equal("Bearer test-token");
288
+ return { data: mockResponse };
289
+ };
290
+
291
+ const result = await getErrorPageTemplate("test-domain", "test-token");
292
+
293
+ expect(result).to.equal("<html>Error: {{error}}</html>");
294
+ });
295
+
296
+ it("should return null on error", async function() {
297
+ axios.get = async function() {
298
+ throw new Error("Template not configured");
299
+ };
300
+
301
+ const result = await getErrorPageTemplate("test-domain", "test-token");
302
+
303
+ expect(result).to.be.null;
304
+ });
305
+ });
306
+
307
+ describe("getTenantSettings", function() {
308
+ it("should return tenant settings on success", async function() {
309
+ const mockSettings = {
310
+ friendly_name: "Test Tenant",
311
+ default_audience: "",
312
+ default_directory: "Username-Password-Authentication",
313
+ session_lifetime: 720
314
+ };
315
+
316
+ axios.get = async function(url, options) {
317
+ expect(url).to.equal("https://test-domain/api/v2/tenants/settings");
318
+ expect(options.headers.Authorization).to.equal("Bearer test-token");
319
+ return { data: mockSettings };
320
+ };
321
+
322
+ const result = await getTenantSettings("test-domain", "test-token");
323
+
324
+ expect(result).to.deep.equal(mockSettings);
325
+ });
326
+
327
+ it("should return empty object on error", async function() {
328
+ axios.get = async function() {
329
+ throw new Error("Settings not accessible");
330
+ };
331
+
332
+ const result = await getTenantSettings("test-domain", "test-token");
333
+
334
+ expect(result).to.deep.equal({});
335
+ });
336
+ });
337
+
338
+ describe("getGuardianFactors", function() {
339
+ it("should return guardian factors on success", async function() {
340
+ const mockFactors = [
341
+ { name: "sms", enabled: true, trial_expired: false },
342
+ { name: "email", enabled: false, trial_expired: false }
343
+ ];
344
+
345
+ axios.get = async function(url, options) {
346
+ expect(url).to.equal("https://test-domain/api/v2/guardian/factors");
347
+ expect(options.headers.Authorization).to.equal("Bearer test-token");
348
+ return { data: mockFactors };
349
+ };
350
+
351
+ const result = await getGuardianFactors("test-domain", "test-token");
352
+
353
+ expect(result).to.deep.equal(mockFactors);
354
+ });
355
+
356
+ it("should return empty object on error", async function() {
357
+ axios.get = async function() {
358
+ throw new Error("MFA not configured");
359
+ };
360
+
361
+ const result = await getGuardianFactors("test-domain", "test-token");
362
+
363
+ expect(result).to.deep.equal({});
364
+ });
365
+ });
366
+
367
+ describe("getGuardianPolicies", function() {
368
+ it("should return guardian policies on success", async function() {
369
+ const mockPolicies = [
370
+ "all-applications",
371
+ "confidence-score"
372
+ ];
373
+
374
+ axios.get = async function(url, options) {
375
+ expect(url).to.equal("https://test-domain/api/v2/guardian/policies");
376
+ expect(options.headers.Authorization).to.equal("Bearer test-token");
377
+ return { data: mockPolicies };
378
+ };
379
+
380
+ const result = await getGuardianPolicies("test-domain", "test-token");
381
+
382
+ expect(result).to.deep.equal(mockPolicies);
383
+ });
384
+
385
+ it("should return empty object on error", async function() {
386
+ axios.get = async function() {
387
+ throw new Error("Policies not found");
388
+ };
389
+
390
+ const result = await getGuardianPolicies("test-domain", "test-token");
391
+
392
+ expect(result).to.deep.equal({});
393
+ });
394
+ });
395
+
396
+ describe("getRules", function() {
397
+ it("should return rules on success", async function() {
398
+ const mockRules = [
399
+ {
400
+ id: "rule_123",
401
+ name: "Add roles",
402
+ script: "function(user, context, callback) { callback(null, user, context); }",
403
+ enabled: true
404
+ }
405
+ ];
406
+
407
+ axios.get = async function(url, options) {
408
+ expect(url).to.equal("https://test-domain/api/v2/rules?enabled=true");
409
+ expect(options.headers.Authorization).to.equal("Bearer test-token");
410
+ return { data: mockRules };
411
+ };
412
+
413
+ const result = await getRules("test-domain", "test-token");
414
+
415
+ expect(result).to.deep.equal(mockRules);
416
+ });
417
+
418
+ it("should return empty array on error", async function() {
419
+ axios.get = async function() {
420
+ throw new Error("Rules not accessible");
421
+ };
422
+
423
+ const result = await getRules("test-domain", "test-token");
424
+
425
+ expect(result).to.deep.equal([]);
426
+ });
427
+ });
428
+
429
+ describe("getHooks", function() {
430
+ it("should return hooks on success", async function() {
431
+ const mockHooks = [
432
+ {
433
+ id: "hook_123",
434
+ name: "Pre User Registration",
435
+ script: "function(user, context, callback) { callback(null, user); }",
436
+ enabled: true
437
+ }
438
+ ];
439
+
440
+ axios.get = async function(url, options) {
441
+ expect(url).to.equal("https://test-domain/api/v2/hooks?enabled=true");
442
+ expect(options.headers.Authorization).to.equal("Bearer test-token");
443
+ return { data: mockHooks };
444
+ };
445
+
446
+ const result = await getHooks("test-domain", "test-token");
447
+
448
+ expect(result).to.deep.equal(mockHooks);
449
+ });
450
+
451
+ it("should return empty array on error", async function() {
452
+ axios.get = async function() {
453
+ throw new Error("Hooks not found");
454
+ };
455
+
456
+ const result = await getHooks("test-domain", "test-token");
457
+
458
+ expect(result).to.deep.equal([]);
459
+ });
460
+ });
461
+
462
+ describe("getActions", function() {
463
+ it("should return actions on success", async function() {
464
+ const mockActions = [
465
+ {
466
+ id: "action_123",
467
+ name: "Custom Action",
468
+ code: "exports.onExecutePostLogin = async (event, api) => {};",
469
+ runtime: "node18"
470
+ }
471
+ ];
472
+
473
+ axios.get = async function(url, options) {
474
+ expect(url).to.equal("https://test-domain/api/v2/actions/actions?installed=false");
475
+ expect(options.headers.Authorization).to.equal("Bearer test-token");
476
+ return { data: mockActions };
477
+ };
478
+
479
+ const result = await getActions("test-domain", "test-token");
480
+
481
+ expect(result).to.deep.equal(mockActions);
482
+ });
483
+
484
+ it("should return empty array on error", async function() {
485
+ axios.get = async function() {
486
+ throw new Error("Actions not accessible");
487
+ };
488
+
489
+ const result = await getActions("test-domain", "test-token");
490
+
491
+ expect(result).to.deep.equal([]);
492
+ });
493
+ });
494
+
495
+ describe("getLogStreams", function() {
496
+ it("should return log streams on success", async function() {
497
+ const mockStreams = [
498
+ {
499
+ id: "lst_123",
500
+ name: "Test Stream",
501
+ type: "http",
502
+ status: "active"
503
+ }
504
+ ];
505
+
506
+ axios.get = async function(url, options) {
507
+ expect(url).to.equal("https://test-domain/api/v2/log-streams");
508
+ expect(options.headers.Authorization).to.equal("Bearer test-token");
509
+ return { data: mockStreams };
510
+ };
511
+
512
+ const result = await getLogStreams("test-domain", "test-token");
513
+
514
+ expect(result).to.deep.equal(mockStreams);
515
+ });
516
+
517
+ it("should return error response data on error", async function() {
518
+ const errorResponse = { response: { data: { error: "Stream not found" } } };
519
+
520
+ axios.get = async function() {
521
+ throw errorResponse;
522
+ };
523
+
524
+ const result = await getLogStreams("test-domain", "test-token");
525
+
526
+ expect(result).to.deep.equal([{ error: "Stream not found" }]);
527
+ });
528
+ });
529
+
530
+ describe("Attack Protection Functions", function() {
531
+ describe("getBruteForceProtectionSetting", function() {
532
+ it("should return brute force protection settings on success", async function() {
533
+ const mockSettings = {
534
+ enabled: true,
535
+ shields: ["block", "user_notification"],
536
+ mode: "count_per_identifier_and_ip"
537
+ };
538
+
539
+ axios.get = async function(url, options) {
540
+ expect(url).to.equal("https://test-domain/api/v2/attack-protection/brute-force-protection");
541
+ expect(options.headers.Authorization).to.equal("Bearer test-token");
542
+ return { data: mockSettings };
543
+ };
544
+
545
+ const result = await getBruteForceProtectionSetting("test-domain", "test-token");
546
+
547
+ expect(result).to.deep.equal(mockSettings);
548
+ });
549
+
550
+ it("should return empty object on error", async function() {
551
+ axios.get = async function() {
552
+ throw new Error("Settings not found");
553
+ };
554
+
555
+ const result = await getBruteForceProtectionSetting("test-domain", "test-token");
556
+
557
+ expect(result).to.deep.equal({});
558
+ });
559
+ });
560
+
561
+ describe("getSuspiciousIpSetting", function() {
562
+ it("should return suspicious IP settings on success", async function() {
563
+ const mockSettings = {
564
+ enabled: true,
565
+ shields: ["admin_notification", "block"],
566
+ allowlist: [],
567
+ stage: {
568
+ "pre-login": { max_attempts: 100, rate: 864000 }
569
+ }
570
+ };
571
+
572
+ axios.get = async function(url) {
573
+ expect(url).to.equal("https://test-domain/api/v2/attack-protection/suspicious-ip-throttling");
574
+ return { data: mockSettings };
575
+ };
576
+
577
+ const result = await getSuspiciousIpSetting("test-domain", "test-token");
578
+
579
+ expect(result).to.deep.equal(mockSettings);
580
+ });
581
+ });
582
+
583
+ describe("getBreachedPasswordSetting", function() {
584
+ it("should return breached password settings on success", async function() {
585
+ const mockSettings = {
586
+ enabled: true,
587
+ shields: ["admin_notification", "block"],
588
+ admin_notification_frequency: ["immediately"],
589
+ method: "standard"
590
+ };
591
+
592
+ axios.get = async function(url) {
593
+ expect(url).to.equal("https://test-domain/api/v2/attack-protection/breached-password-detection");
594
+ return { data: mockSettings };
595
+ };
596
+
597
+ const result = await getBreachedPasswordSetting("test-domain", "test-token");
598
+
599
+ expect(result).to.deep.equal(mockSettings);
600
+ });
601
+ });
602
+
603
+ describe("getBotDetectionSetting", function() {
604
+ it("should return bot detection settings on success", async function() {
605
+ const mockSettings = {
606
+ enabled: true,
607
+ provider: "recaptcha_v2"
608
+ };
609
+
610
+ axios.get = async function(url) {
611
+ expect(url).to.equal("https://test-domain/api/v2/anomaly/captchas");
612
+ return { data: mockSettings };
613
+ };
614
+
615
+ const result = await getBotDetectionSetting("test-domain", "test-token");
616
+
617
+ expect(result).to.deep.equal(mockSettings);
618
+ });
619
+ });
620
+
621
+ describe("getAttackProtection", function() {
622
+ it("should return combined attack protection settings", async function() {
623
+ const mockBreached = { enabled: true };
624
+ const mockBrute = { enabled: true };
625
+ const mockSuspicious = { enabled: false };
626
+ const mockBot = { enabled: true };
627
+
628
+ let callCount = 0;
629
+ axios.get = async function(url) {
630
+ callCount++;
631
+ if (url.includes("breached-password-detection")) {
632
+ return { data: mockBreached };
633
+ } else if (url.includes("brute-force-protection")) {
634
+ return { data: mockBrute };
635
+ } else if (url.includes("suspicious-ip-throttling")) {
636
+ return { data: mockSuspicious };
637
+ } else if (url.includes("captchas")) {
638
+ return { data: mockBot };
639
+ }
640
+ };
641
+
642
+ const result = await getAttackProtection("test-domain", "test-token");
643
+
644
+ expect(result).to.have.property("breachedPasswordDetection");
645
+ expect(result).to.have.property("bruteForceProtection");
646
+ expect(result).to.have.property("suspiciousIpThrottling");
647
+ expect(result).to.have.property("botDetection");
648
+ expect(callCount).to.equal(4);
649
+ });
650
+
651
+ it("should return empty object on error", async function() {
652
+ axios.get = async function() {
653
+ throw new Error("Attack protection not accessible");
654
+ };
655
+
656
+ const result = await getAttackProtection("test-domain", "test-token");
657
+
658
+ // The actual function returns an object with empty sub-objects, not an empty object
659
+ expect(result).to.have.property("breachedPasswordDetection");
660
+ expect(result).to.have.property("bruteForceProtection");
661
+ expect(result).to.have.property("suspiciousIpThrottling");
662
+ expect(result).to.have.property("botDetection");
663
+ expect(result.breachedPasswordDetection).to.deep.equal({});
664
+ expect(result.bruteForceProtection).to.deep.equal({});
665
+ expect(result.suspiciousIpThrottling).to.deep.equal({});
666
+ expect(result.botDetection).to.deep.equal({});
667
+ });
668
+ });
669
+ });
670
+
671
+ describe("getLogs", function() {
672
+ it("should return logs with query on success", async function() {
673
+ const mockLogs = [
674
+ { type: "s", hostname: "test-domain" },
675
+ { type: "f", hostname: "test-domain" }
676
+ ];
677
+
678
+ axios.get = async function(url, options) {
679
+ expect(url).to.match(/https:\/\/test-domain\/api\/v2\/logs\?per_page=1&fields=type,hostname&q=type:/);
680
+ expect(options.headers.Authorization).to.equal("Bearer test-token");
681
+ return { data: mockLogs };
682
+ };
683
+
684
+ const result = await getLogs("test-domain", "test-token");
685
+
686
+ expect(result).to.have.property("log_query");
687
+ expect(result).to.have.property("logs");
688
+ expect(result.logs).to.deep.equal(mockLogs);
689
+ });
690
+
691
+ it("should return empty logs array on error", async function() {
692
+ axios.get = async function() {
693
+ throw new Error("Logs not accessible");
694
+ };
695
+
696
+ const result = await getLogs("test-domain", "test-token");
697
+
698
+ expect(result).to.have.property("log_query");
699
+ expect(result.logs).to.deep.equal([]);
700
+ });
701
+ });
702
+
703
+ describe("getNetworkACL", function() {
704
+ it("should return network ACL settings on success", async function() {
705
+ const mockACL = [
706
+ {
707
+ id: "acl_123",
708
+ name: "Test ACL",
709
+ ip_ranges: ["192.168.1.0/24"]
710
+ }
711
+ ];
712
+
713
+ axios.get = async function(url) {
714
+ expect(url).to.equal("https://test-domain/api/v2/network-acls");
715
+ return { data: mockACL };
716
+ };
717
+
718
+ const result = await getNetworkACL("test-domain", "test-token");
719
+
720
+ expect(result).to.deep.equal(mockACL);
721
+ });
722
+
723
+ it("should return error response on failure", async function() {
724
+ const errorData = { message: "Feature not available", statusCode: 404 };
725
+ const error = new Error("Not found");
726
+ error.response = { data: errorData };
727
+
728
+ axios.get = async function() {
729
+ throw error;
730
+ };
731
+
732
+ const result = await getNetworkACL("test-domain", "test-token");
733
+
734
+ expect(result).to.deep.equal([errorData]);
735
+ });
736
+ });
737
+
738
+ describe("getEventStreams", function() {
739
+ it("should return event streams on success", async function() {
740
+ const mockEventStreams = [
741
+ {
742
+ id: "ev_123",
743
+ name: "User Events Stream",
744
+ type: "http",
745
+ status: "active"
746
+ }
747
+ ];
748
+
749
+ axios.get = async function(url) {
750
+ expect(url).to.equal("https://test-domain/api/v2/event-streams");
751
+ return { data: { eventStreams: mockEventStreams } };
752
+ };
753
+
754
+ const result = await getEventStreams("test-domain", "test-token");
755
+
756
+ expect(result).to.deep.equal(mockEventStreams);
757
+ });
758
+
759
+ it("should return error response on failure", async function() {
760
+ const errorData = { error: "Event streams not available" };
761
+ const error = new Error("Service unavailable");
762
+ error.response = { data: errorData };
763
+
764
+ axios.get = async function() {
765
+ throw error;
766
+ };
767
+
768
+ const result = await getEventStreams("test-domain", "test-token");
769
+
770
+ expect(result).to.deep.equal([errorData]);
771
+ });
772
+ });
773
+
774
+ describe("Edge cases and error handling", function() {
775
+ it("should handle malformed response data", async function() {
776
+ axios.get = async function() {
777
+ return { data: null };
778
+ };
779
+
780
+ const result = await getCustomDomains("test-domain", "test-token");
781
+
782
+ expect(result).to.be.null;
783
+ });
784
+
785
+ it("should handle network timeouts", async function() {
786
+ axios.get = async function() {
787
+ const error = new Error("timeout of 5000ms exceeded");
788
+ error.code = "ECONNABORTED";
789
+ throw error;
790
+ };
791
+
792
+ const result = await getTenantSettings("test-domain", "test-token");
793
+
794
+ expect(result).to.deep.equal({});
795
+ });
796
+
797
+ it("should handle 401 unauthorized errors", async function() {
798
+ axios.get = async function() {
799
+ const error = new Error("Request failed with status code 401");
800
+ error.response = { status: 401, data: { error: "Unauthorized" } };
801
+ throw error;
802
+ };
803
+
804
+ const result = await getEmailProvider("test-domain", "invalid-token");
805
+
806
+ expect(result).to.be.null;
807
+ });
808
+
809
+ it("should handle 403 forbidden errors", async function() {
810
+ axios.get = async function() {
811
+ const error = new Error("Request failed with status code 403");
812
+ error.response = { status: 403, data: { error: "Insufficient scope" } };
813
+ throw error;
814
+ };
815
+
816
+ const result = await getActions("test-domain", "limited-token");
817
+
818
+ expect(result).to.deep.equal([]);
819
+ });
820
+
821
+ it("should handle 404 not found errors", async function() {
822
+ axios.get = async function() {
823
+ const error = new Error("Request failed with status code 404");
824
+ error.response = { status: 404, data: { error: "Resource not found" } };
825
+ throw error;
826
+ };
827
+
828
+ const result = await getErrorPageTemplate("test-domain", "test-token");
829
+
830
+ expect(result).to.be.null;
831
+ });
832
+ });
833
+ });