@auth0/auth0-checkmate 1.6.18 → 1.7.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/analyzer/lib/clients/checkDPoP.js +71 -0
- package/analyzer/lib/resource_servers/checkAPISigningAlgorithm.js +76 -0
- package/analyzer/lib/resource_servers/checkAPITokenLifetime.js +111 -0
- package/analyzer/report.js +38 -0
- package/analyzer/tools/auth0.js +15 -0
- package/locales/en.json +94 -1
- package/package.json +1 -1
- package/tests/clients/checkDPoP.test.js +190 -0
- package/tests/resource_servers/checkAPISigningAlgorithm.test.js +132 -0
- package/tests/resource_servers/checkAPITokenLifetime.test.js +181 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
{
|
|
3
|
+
clients: [
|
|
4
|
+
{
|
|
5
|
+
"tenant": "contos0",
|
|
6
|
+
"global": false,
|
|
7
|
+
"name": "Default App",
|
|
8
|
+
"is_first_party": true,
|
|
9
|
+
"client_id": "client_id",
|
|
10
|
+
"app_type": "
|
|
11
|
+
",
|
|
12
|
+
"grant_types": [
|
|
13
|
+
"authorization_code",
|
|
14
|
+
"refresh_token"
|
|
15
|
+
],
|
|
16
|
+
"require_proof_of_possession": true
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
*/
|
|
21
|
+
const _ = require("lodash");
|
|
22
|
+
const executeCheck = require("../executeCheck");
|
|
23
|
+
const CONSTANTS = require("../constants");
|
|
24
|
+
|
|
25
|
+
const DPOP_RELEVANT_GRANT_TYPES = [
|
|
26
|
+
"authorization_code",
|
|
27
|
+
"refresh_token",
|
|
28
|
+
"client_credentials",
|
|
29
|
+
"password",
|
|
30
|
+
"urn:ietf:params:oauth:grant-type:device_code",
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function validateDPoPForApp(app) {
|
|
34
|
+
const enabledGrantTypes = app.grant_types || [];
|
|
35
|
+
const report = [];
|
|
36
|
+
|
|
37
|
+
const hasDPoPRelevantGrant = DPOP_RELEVANT_GRANT_TYPES.some((g) =>
|
|
38
|
+
enabledGrantTypes.includes(g)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if (hasDPoPRelevantGrant && app.require_proof_of_possession !== true) {
|
|
42
|
+
report.push({
|
|
43
|
+
name: app.client_id ? app.name.concat(` (${app.client_id})`) : app.name,
|
|
44
|
+
client_id: app.client_id,
|
|
45
|
+
field: "require_proof_of_possession",
|
|
46
|
+
status: CONSTANTS.FAIL,
|
|
47
|
+
value: app.require_proof_of_possession ?? false,
|
|
48
|
+
is_first_party: app.is_first_party
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return report;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function checkDPoP(options) {
|
|
56
|
+
return executeCheck("checkDPoP", (callback) => {
|
|
57
|
+
const { clients } = options || [];
|
|
58
|
+
const reports = [];
|
|
59
|
+
if (_.isEmpty(clients)) {
|
|
60
|
+
return callback(reports);
|
|
61
|
+
}
|
|
62
|
+
clients.forEach((client) => {
|
|
63
|
+
var report = validateDPoPForApp(client);
|
|
64
|
+
var name = client.name.concat(` (${client.client_id})`);
|
|
65
|
+
reports.push({ name: name, report: report });
|
|
66
|
+
});
|
|
67
|
+
return callback(reports);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = checkDPoP;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*
|
|
2
|
+
{
|
|
3
|
+
"id": "xxxxxxxxxxxx",
|
|
4
|
+
"name": "test wrong alg",
|
|
5
|
+
"identifier": "test1",
|
|
6
|
+
"allow_offline_access": false,
|
|
7
|
+
"skip_consent_for_verifiable_first_party_clients": true,
|
|
8
|
+
"subject_type_authorization": {
|
|
9
|
+
"user": {
|
|
10
|
+
"policy": "require_client_grant"
|
|
11
|
+
},
|
|
12
|
+
"client": {
|
|
13
|
+
"policy": "require_client_grant"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"token_lifetime": 86400,
|
|
17
|
+
"token_lifetime_for_web": 7200,
|
|
18
|
+
"signing_alg": "HS256",
|
|
19
|
+
"signing_secret": "xxxxxxxxxxxxxxxxxx",
|
|
20
|
+
"token_dialect": "access_token"
|
|
21
|
+
}
|
|
22
|
+
*/
|
|
23
|
+
const _ = require("lodash");
|
|
24
|
+
const executeCheck = require("../executeCheck");
|
|
25
|
+
const CONSTANTS = require("../constants");
|
|
26
|
+
|
|
27
|
+
function checkAPISigningAlgorithm(options) {
|
|
28
|
+
return executeCheck("checkAPISigningAlgorithm", (callback) => {
|
|
29
|
+
const { resourceServers } = options;
|
|
30
|
+
const reports = [];
|
|
31
|
+
|
|
32
|
+
if (_.isEmpty(resourceServers)) {
|
|
33
|
+
return callback(reports);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
resourceServers.forEach((api) => {
|
|
37
|
+
// Skip the Auth0 Management API (system API)
|
|
38
|
+
if (api.is_system) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const report = [];
|
|
43
|
+
const apiDisplayName = api.identifier
|
|
44
|
+
? `${api.name} (${api.identifier})`
|
|
45
|
+
: api.name;
|
|
46
|
+
|
|
47
|
+
if (api.signing_alg === "HS256") {
|
|
48
|
+
report.push({
|
|
49
|
+
name: apiDisplayName,
|
|
50
|
+
api_name: api.name,
|
|
51
|
+
field: "using_symmetric_alg",
|
|
52
|
+
status: CONSTANTS.FAIL,
|
|
53
|
+
value: api.signing_alg,
|
|
54
|
+
identifier: api.identifier,
|
|
55
|
+
});
|
|
56
|
+
} else {
|
|
57
|
+
report.push({
|
|
58
|
+
name: apiDisplayName,
|
|
59
|
+
api_name: api.name,
|
|
60
|
+
field: "using_asymmetric_alg",
|
|
61
|
+
status: CONSTANTS.SUCCESS,
|
|
62
|
+
value: api.signing_alg || "RS256",
|
|
63
|
+
identifier: api.identifier,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (report.length > 0) {
|
|
68
|
+
reports.push({ name: apiDisplayName, report: report });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return callback(reports);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = checkAPISigningAlgorithm;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/*
|
|
2
|
+
{
|
|
3
|
+
"id": "xxxxxxxxx",
|
|
4
|
+
"name": "Test long token",
|
|
5
|
+
"identifier": "test2",
|
|
6
|
+
"allow_offline_access": true,
|
|
7
|
+
"skip_consent_for_verifiable_first_party_clients": true,
|
|
8
|
+
"subject_type_authorization": {
|
|
9
|
+
"client": {
|
|
10
|
+
"policy": "require_client_grant"
|
|
11
|
+
},
|
|
12
|
+
"user": {
|
|
13
|
+
"policy": "allow_all"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"token_lifetime": 864000,
|
|
17
|
+
"token_lifetime_for_web": 7200,
|
|
18
|
+
"signing_alg": "RS256",
|
|
19
|
+
"signing_secret": "xxxxxxxxxxxxxx",
|
|
20
|
+
"enforce_policies": false,
|
|
21
|
+
"token_dialect": "access_token"
|
|
22
|
+
}
|
|
23
|
+
*/
|
|
24
|
+
const _ = require("lodash");
|
|
25
|
+
const executeCheck = require("../executeCheck");
|
|
26
|
+
const CONSTANTS = require("../constants");
|
|
27
|
+
|
|
28
|
+
// Token lifetime thresholds (in seconds)
|
|
29
|
+
const TOKEN_LIFETIME_DEFAULT = 86400; // 24 hours - Auth0 default
|
|
30
|
+
const TOKEN_LIFETIME_WARNING = 604800; // 7 days
|
|
31
|
+
|
|
32
|
+
function checkAPITokenLifetime(options) {
|
|
33
|
+
return executeCheck("checkAPITokenLifetime", (callback) => {
|
|
34
|
+
const { resourceServers } = options;
|
|
35
|
+
const reports = [];
|
|
36
|
+
|
|
37
|
+
if (_.isEmpty(resourceServers)) {
|
|
38
|
+
return callback(reports);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
resourceServers.forEach((api) => {
|
|
42
|
+
// Skip the Auth0 Management API (system API)
|
|
43
|
+
if (api.is_system) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const report = [];
|
|
48
|
+
const apiDisplayName = api.identifier
|
|
49
|
+
? `${api.name} (${api.identifier})`
|
|
50
|
+
: api.name;
|
|
51
|
+
|
|
52
|
+
const tokenLifetime = api.token_lifetime || TOKEN_LIFETIME_DEFAULT;
|
|
53
|
+
|
|
54
|
+
if (tokenLifetime >= TOKEN_LIFETIME_WARNING) {
|
|
55
|
+
report.push({
|
|
56
|
+
name: apiDisplayName,
|
|
57
|
+
api_name: api.name,
|
|
58
|
+
field: "token_lifetime_too_long",
|
|
59
|
+
status: CONSTANTS.FAIL,
|
|
60
|
+
value: formatDuration(tokenLifetime),
|
|
61
|
+
identifier: api.identifier,
|
|
62
|
+
});
|
|
63
|
+
} else if (tokenLifetime > TOKEN_LIFETIME_DEFAULT) {
|
|
64
|
+
report.push({
|
|
65
|
+
name: apiDisplayName,
|
|
66
|
+
api_name: api.name,
|
|
67
|
+
field: "token_lifetime_extended",
|
|
68
|
+
status: CONSTANTS.WARN,
|
|
69
|
+
value: formatDuration(tokenLifetime),
|
|
70
|
+
identifier: api.identifier,
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
report.push({
|
|
74
|
+
name: apiDisplayName,
|
|
75
|
+
api_name: api.name,
|
|
76
|
+
field: "token_lifetime_appropriate",
|
|
77
|
+
status: CONSTANTS.SUCCESS,
|
|
78
|
+
value: formatDuration(tokenLifetime),
|
|
79
|
+
identifier: api.identifier,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (report.length > 0) {
|
|
84
|
+
reports.push({ name: apiDisplayName, report: report });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return callback(reports);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Format duration in seconds to human-readable string
|
|
94
|
+
* @param {number} seconds - Duration in seconds
|
|
95
|
+
* @returns {string} - Formatted duration (e.g., "24 hours", "7 days")
|
|
96
|
+
*/
|
|
97
|
+
function formatDuration(seconds) {
|
|
98
|
+
if (seconds >= 86400) {
|
|
99
|
+
const days = Math.floor(seconds / 86400);
|
|
100
|
+
return `${days} day${days > 1 ? "s" : ""} (${seconds} seconds)`;
|
|
101
|
+
} else if (seconds >= 3600) {
|
|
102
|
+
const hours = Math.floor(seconds / 3600);
|
|
103
|
+
return `${hours} hour${hours > 1 ? "s" : ""} (${seconds} seconds)`;
|
|
104
|
+
} else if (seconds >= 60) {
|
|
105
|
+
const minutes = Math.floor(seconds / 60);
|
|
106
|
+
return `${minutes} minute${minutes > 1 ? "s" : ""} (${seconds} seconds)`;
|
|
107
|
+
}
|
|
108
|
+
return `${seconds} seconds`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = checkAPITokenLifetime;
|
package/analyzer/report.js
CHANGED
|
@@ -24,6 +24,7 @@ const {
|
|
|
24
24
|
getLogs,
|
|
25
25
|
getNetworkACL,
|
|
26
26
|
getEventStreams,
|
|
27
|
+
getResourceServers,
|
|
27
28
|
} = require("./tools/auth0");
|
|
28
29
|
|
|
29
30
|
const logger = require("./lib/logger");
|
|
@@ -165,7 +166,13 @@ async function generateReport(locale, tenantConfig, config) {
|
|
|
165
166
|
config.auth0Domain,
|
|
166
167
|
config.auth0MgmtToken
|
|
167
168
|
);
|
|
169
|
+
|
|
170
|
+
tenantConfig.resourceServers = await getResourceServers(
|
|
171
|
+
config.auth0Domain,
|
|
172
|
+
config.auth0MgmtToken
|
|
173
|
+
);
|
|
168
174
|
}
|
|
175
|
+
|
|
169
176
|
const statusOrder = ["green", "amber", "red"];
|
|
170
177
|
let fullReport =
|
|
171
178
|
(await runProductionChecks(tenantConfig, config.selectedValidators)) ||
|
|
@@ -381,6 +388,19 @@ async function generateReport(locale, tenantConfig, config) {
|
|
|
381
388
|
// Replace original report.details with the new structure
|
|
382
389
|
report.details = transformedDetails;
|
|
383
390
|
break;
|
|
391
|
+
case "checkDPoP":
|
|
392
|
+
report.disclaimer = i18n.__(`${report.name}.disclaimer`);
|
|
393
|
+
report.advisory = i18n.__(`${report.name}.advisory`);
|
|
394
|
+
grouped = _.groupBy(report.details, "name");
|
|
395
|
+
res = tranformReport(grouped);
|
|
396
|
+
res.forEach((item) => {
|
|
397
|
+
item.values.forEach((detail) => {
|
|
398
|
+
detail.report.forEach((c) => {
|
|
399
|
+
c.message = i18n.__(`${report.name}.${c.field}`, c.value);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
break;
|
|
384
404
|
case "checkDependencies":
|
|
385
405
|
report.details.forEach((cd) => {
|
|
386
406
|
cd.message = i18n.__(`${report.name}.${cd.field}`, cd.value);
|
|
@@ -393,6 +413,24 @@ async function generateReport(locale, tenantConfig, config) {
|
|
|
393
413
|
cd.message = i18n.__(`${report.name}.${cd.field}`, cd.value);
|
|
394
414
|
});
|
|
395
415
|
break;
|
|
416
|
+
case "checkAPISigningAlgorithm":
|
|
417
|
+
case "checkAPITokenLifetime":
|
|
418
|
+
report.advisory = i18n.__(`${report.name}.advisory`);
|
|
419
|
+
grouped = _.groupBy(report.details, "name");
|
|
420
|
+
res = tranformReport(grouped);
|
|
421
|
+
res.forEach((api) => {
|
|
422
|
+
api.values.forEach((detail) => {
|
|
423
|
+
detail.report.forEach((c) => {
|
|
424
|
+
c.name = api.name;
|
|
425
|
+
c.message = i18n.__(
|
|
426
|
+
`${report.name}.${c.field}`,
|
|
427
|
+
c.api_name,
|
|
428
|
+
c.value
|
|
429
|
+
);
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
break;
|
|
396
434
|
default:
|
|
397
435
|
report.details.forEach((cd) => {
|
|
398
436
|
cd.message = i18n.__(`${report.name}.${cd.field}`, cd.value);
|
package/analyzer/tools/auth0.js
CHANGED
|
@@ -463,6 +463,20 @@ async function getEventStreams(domain, accessToken) {
|
|
|
463
463
|
return [error.response.data];
|
|
464
464
|
}
|
|
465
465
|
}
|
|
466
|
+
|
|
467
|
+
async function getResourceServers(domain, accessToken) {
|
|
468
|
+
const url = `https://${domain}/api/v2/resource-servers`;
|
|
469
|
+
const headers = { Authorization: `Bearer ${accessToken}` };
|
|
470
|
+
logger.log("info", `Getting resource servers (APIs)`);
|
|
471
|
+
|
|
472
|
+
try {
|
|
473
|
+
const response = await axios.get(url, { headers });
|
|
474
|
+
return response.data;
|
|
475
|
+
} catch (error) {
|
|
476
|
+
logger.log("error", `Failed to get resource servers ${error.message}`);
|
|
477
|
+
return [];
|
|
478
|
+
}
|
|
479
|
+
}
|
|
466
480
|
module.exports = {
|
|
467
481
|
getAccessToken,
|
|
468
482
|
getCustomDomains,
|
|
@@ -486,4 +500,5 @@ module.exports = {
|
|
|
486
500
|
getLogs,
|
|
487
501
|
getNetworkACL,
|
|
488
502
|
getEventStreams,
|
|
503
|
+
getResourceServers,
|
|
489
504
|
};
|
package/locales/en.json
CHANGED
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
"Grant Types",
|
|
51
51
|
"JWT Signing Algorithm",
|
|
52
52
|
"Cross Origin Authentication",
|
|
53
|
-
"Refresh Tokens"
|
|
53
|
+
"Refresh Tokens",
|
|
54
|
+
"Token Sender-Constraining"
|
|
54
55
|
]
|
|
55
56
|
},
|
|
56
57
|
{
|
|
@@ -154,6 +155,14 @@
|
|
|
154
155
|
"title": "Event Streams (Early Access Capability)",
|
|
155
156
|
"required_scope": "read:event_streamss",
|
|
156
157
|
"items": []
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"title": "Resource Servers (APIs)",
|
|
161
|
+
"required_scope": "read:resource_servers",
|
|
162
|
+
"items": [
|
|
163
|
+
"Signing Algorithm",
|
|
164
|
+
"Token Lifetime"
|
|
165
|
+
]
|
|
157
166
|
}
|
|
158
167
|
],
|
|
159
168
|
"validator_summary": "Checked <b>%s</b> validators against tenant <b>%s</b> in total.",
|
|
@@ -1476,5 +1485,89 @@
|
|
|
1476
1485
|
"https://auth0.com/docs/customize/events/events-best-practices",
|
|
1477
1486
|
"https://auth0.com/docs/customize/events/event-testing-observability-and-failure-recovery"
|
|
1478
1487
|
]
|
|
1488
|
+
},
|
|
1489
|
+
"checkAPISigningAlgorithm": {
|
|
1490
|
+
"title": "Resource Servers (APIs) - Signing Algorithm",
|
|
1491
|
+
"category": "Resource Servers (APIs)",
|
|
1492
|
+
"description": "Validates that APIs use RS256 signing algorithm instead of HS256.",
|
|
1493
|
+
"docsPath": [
|
|
1494
|
+
"https://auth0.com/docs/get-started/apis/api-settings"
|
|
1495
|
+
],
|
|
1496
|
+
"severity": "Moderate",
|
|
1497
|
+
"status": "yellow",
|
|
1498
|
+
"severity_message": "%s API(s) using insecure signing algorithm",
|
|
1499
|
+
"advisory": {
|
|
1500
|
+
"issue": "Insecure Token Signing Algorithm",
|
|
1501
|
+
"description": {
|
|
1502
|
+
"what_it_is": "The signing algorithm determines how access tokens are cryptographically signed.",
|
|
1503
|
+
"why_its_risky": [
|
|
1504
|
+
"HS256 requires sharing the signing secret with your API, increasing the risk of secret exposure. Anyone with the HS256 secret can forge valid tokens."
|
|
1505
|
+
]
|
|
1506
|
+
},
|
|
1507
|
+
"how_to_fix": [
|
|
1508
|
+
"Use RS256 (asymmetric) signing algorithm for new APIs. It is recommended because the token will be signed with your tenant private key."
|
|
1509
|
+
]
|
|
1510
|
+
},
|
|
1511
|
+
"using_symmetric_alg": "API \"%s\" is using HS256 signing algorithm. RS256 is recommended for better security.",
|
|
1512
|
+
"using_asymmetric_alg": "API \"%s\" is using %s signing algorithm."
|
|
1513
|
+
},
|
|
1514
|
+
"checkAPITokenLifetime": {
|
|
1515
|
+
"title": "Resource Servers (APIs) - Token Lifetime",
|
|
1516
|
+
"category": "Resource Servers (APIs)",
|
|
1517
|
+
"description": "Validates that API access token lifetimes are appropriately configured. Shorter lifetimes reduce the window of opportunity if tokens are compromised. Auth0 recommends that sensitive APIs use shorter token lifetimes.",
|
|
1518
|
+
"docsPath": [
|
|
1519
|
+
"https://auth0.com/docs/get-started/apis/api-settings"
|
|
1520
|
+
],
|
|
1521
|
+
"severity": "Info",
|
|
1522
|
+
"status": "blue",
|
|
1523
|
+
"severity_message": "%s API(s) have extended token lifetimes",
|
|
1524
|
+
"advisory": {
|
|
1525
|
+
"issue": "Extended Access Token Lifetime",
|
|
1526
|
+
"description": {
|
|
1527
|
+
"what_it_is": "Token lifetime determines how long an access token remains valid. Auth0's default is 24 hours (86400 seconds), with a maximum of 30 days.",
|
|
1528
|
+
"why_its_risky": [
|
|
1529
|
+
"Long-lived tokens increase the window of opportunity for attackers if tokens are compromised.",
|
|
1530
|
+
"Auth0 recommends that sensitive APIs use shorter token lifetimes (e.g., a banking API should expire more quickly than a to-do API)."
|
|
1531
|
+
]
|
|
1532
|
+
},
|
|
1533
|
+
"how_to_fix": [
|
|
1534
|
+
"Review token lifetime settings for each API based on sensitivity.",
|
|
1535
|
+
"Use shorter lifetimes (e.g., 1 hour) for sensitive APIs."
|
|
1536
|
+
]
|
|
1537
|
+
},
|
|
1538
|
+
"token_lifetime_too_long": "API \"%s\" has an access token lifetime of %s. This exceeds the recommended maximum of 7 days. Consider reducing the lifetime for sensitive APIs.",
|
|
1539
|
+
"token_lifetime_extended": "API \"%s\" has an access token lifetime of %s. This exceeds the default of 24 hours. Review if this is appropriate for your security requirements.",
|
|
1540
|
+
"token_lifetime_appropriate": "API \"%s\" has an access token lifetime of %s."
|
|
1541
|
+
},
|
|
1542
|
+
"checkDPoP": {
|
|
1543
|
+
"title": "Applications - Token Sender-Constraining",
|
|
1544
|
+
"category": "Applications",
|
|
1545
|
+
"description": "Token Sender-Constraining cryptographically binds access tokens to the client that requested them, preventing stolen tokens from being replayed by an attacker. We recommend enabling Token Sender-Constraining for all applications using the authorization code, refresh token, client credentials, password (ROPC), or device code grant types.",
|
|
1546
|
+
"docsPath": [
|
|
1547
|
+
"https://auth0.com/docs/secure/sender-constraining"
|
|
1548
|
+
],
|
|
1549
|
+
"severity": "Low",
|
|
1550
|
+
"status": "green",
|
|
1551
|
+
"disclaimer": "Token Sender-Constraining for applications is available only to customers on an Enterprise plan.",
|
|
1552
|
+
"advisory": {
|
|
1553
|
+
"issue": "Token Sender-Constraining Not Enabled for Application",
|
|
1554
|
+
"description": {
|
|
1555
|
+
"what_it_is": "Token Sender-Constraining is an Auth0 security setting that cryptographically binds access tokens to the requesting client's key pair. When enabled, a token stolen in transit cannot be replayed by an attacker because they would also need possession of the client's private key.",
|
|
1556
|
+
"why_its_risky": [
|
|
1557
|
+
"Without Token Sender-Constraining, bearer tokens can be used by any party that obtains them, making token theft a critical threat vector.",
|
|
1558
|
+
"Access tokens intercepted via network attacks, XSS, or compromised infrastructure can be replayed until expiry with no way to detect misuse.",
|
|
1559
|
+
"Applications using refresh tokens without sender-constraining are particularly exposed if tokens are leaked from client storage or logs.",
|
|
1560
|
+
"Bearer token misuse may go undetected since there is no cryptographic binding to identify the legitimate caller."
|
|
1561
|
+
]
|
|
1562
|
+
},
|
|
1563
|
+
"how_to_fix": [
|
|
1564
|
+
"In the Auth0 Dashboard navigate to Applications > [Your App] > Settings > Token Sender-Constraining and enable the setting.",
|
|
1565
|
+
"An API (Resource Server) must also be configured to require the Sender Constraining Method such as DPoP or mTLS. If the API doesn't check for the cryptographic binding, the extra security is ignored",
|
|
1566
|
+
"Ensure your client SDK or library supports generating and sending DPoP proofs with each token request."
|
|
1567
|
+
]
|
|
1568
|
+
},
|
|
1569
|
+
"severity_message": "%s applications / clients do not have Token Sender-Constraining enabled",
|
|
1570
|
+
"require_proof_of_possession": "Token Sender-Constraining is not enabled for this application (require_proof_of_possession: %s)",
|
|
1571
|
+
"undefined": "Token Sender-Constraining is not enabled for this application"
|
|
1479
1572
|
}
|
|
1480
1573
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
const chai = require("chai");
|
|
2
|
+
const expect = chai.expect;
|
|
3
|
+
|
|
4
|
+
const checkDPoP = require("../../analyzer/lib/clients/checkDPoP");
|
|
5
|
+
const CONSTANTS = require("../../analyzer/lib/constants");
|
|
6
|
+
|
|
7
|
+
describe("checkDPoP", function() {
|
|
8
|
+
|
|
9
|
+
it("should report failure if authorization_code grant is used and Token Sender-Constraining is not enabled", function() {
|
|
10
|
+
const options = {
|
|
11
|
+
clients: [{
|
|
12
|
+
name: "Web App",
|
|
13
|
+
client_id: "client_web",
|
|
14
|
+
app_type: "regular_web",
|
|
15
|
+
is_first_party: true,
|
|
16
|
+
grant_types: ["authorization_code"],
|
|
17
|
+
require_proof_of_possession: false,
|
|
18
|
+
}]
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
checkDPoP(options, (result) => {
|
|
22
|
+
expect(result).to.be.an("array").with.lengthOf(1);
|
|
23
|
+
expect(result[0].name).to.equal("Web App (client_web)");
|
|
24
|
+
expect(result[0].report).to.be.an("array").with.lengthOf(1);
|
|
25
|
+
expect(result[0].report[0]).to.include({
|
|
26
|
+
name: "Web App (client_web)",
|
|
27
|
+
client_id: "client_web",
|
|
28
|
+
field: "require_proof_of_possession",
|
|
29
|
+
status: CONSTANTS.FAIL,
|
|
30
|
+
value: false,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should report failure if refresh_token grant is used and Token Sender-Constraining is not enabled", function() {
|
|
36
|
+
const options = {
|
|
37
|
+
clients: [{
|
|
38
|
+
name: "SPA App",
|
|
39
|
+
client_id: "client_spa",
|
|
40
|
+
app_type: "spa",
|
|
41
|
+
is_first_party: true,
|
|
42
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
43
|
+
require_proof_of_possession: false,
|
|
44
|
+
}]
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
checkDPoP(options, (result) => {
|
|
48
|
+
expect(result).to.be.an("array").with.lengthOf(1);
|
|
49
|
+
expect(result[0].report).to.be.an("array").with.lengthOf(1);
|
|
50
|
+
expect(result[0].report[0]).to.include({
|
|
51
|
+
field: "require_proof_of_possession",
|
|
52
|
+
status: CONSTANTS.FAIL,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should report failure if Token Sender-Constraining is absent", function() {
|
|
58
|
+
const options = {
|
|
59
|
+
clients: [{
|
|
60
|
+
name: "Legacy App",
|
|
61
|
+
client_id: "client_legacy",
|
|
62
|
+
app_type: "regular_web",
|
|
63
|
+
is_first_party: true,
|
|
64
|
+
grant_types: ["authorization_code"],
|
|
65
|
+
}]
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
checkDPoP(options, (result) => {
|
|
69
|
+
expect(result[0].report).to.be.an("array").with.lengthOf(1);
|
|
70
|
+
expect(result[0].report[0]).to.include({
|
|
71
|
+
field: "require_proof_of_possession",
|
|
72
|
+
status: CONSTANTS.FAIL,
|
|
73
|
+
value: false,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should return empty report if Token Sender-Constraining is enabled", function() {
|
|
79
|
+
const options = {
|
|
80
|
+
clients: [{
|
|
81
|
+
name: "Secure App",
|
|
82
|
+
client_id: "client_secure",
|
|
83
|
+
app_type: "spa",
|
|
84
|
+
is_first_party: true,
|
|
85
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
86
|
+
require_proof_of_possession: true,
|
|
87
|
+
}]
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
checkDPoP(options, (result) => {
|
|
91
|
+
expect(result).to.be.an("array").with.lengthOf(1);
|
|
92
|
+
expect(result[0].name).to.equal("Secure App (client_secure)");
|
|
93
|
+
expect(result[0].report).to.be.an("array").that.is.empty;
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should report failure if client_credentials grant is used and Token Sender-Constraining is not enabled", function() {
|
|
98
|
+
const options = {
|
|
99
|
+
clients: [{
|
|
100
|
+
name: "M2M App",
|
|
101
|
+
client_id: "client_m2m",
|
|
102
|
+
app_type: "non_interactive",
|
|
103
|
+
is_first_party: true,
|
|
104
|
+
grant_types: ["client_credentials"],
|
|
105
|
+
require_proof_of_possession: false,
|
|
106
|
+
}]
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
checkDPoP(options, (result) => {
|
|
110
|
+
expect(result).to.be.an("array").with.lengthOf(1);
|
|
111
|
+
expect(result[0].report).to.be.an("array").with.lengthOf(1);
|
|
112
|
+
expect(result[0].report[0]).to.include({
|
|
113
|
+
field: "require_proof_of_possession",
|
|
114
|
+
status: CONSTANTS.FAIL,
|
|
115
|
+
value: false,
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should report failure if password grant is used and Token Sender-Constraining is not enabled", function() {
|
|
121
|
+
const options = {
|
|
122
|
+
clients: [{
|
|
123
|
+
name: "ROPC App",
|
|
124
|
+
client_id: "client_ropc",
|
|
125
|
+
app_type: "regular_web",
|
|
126
|
+
is_first_party: true,
|
|
127
|
+
grant_types: ["password"],
|
|
128
|
+
require_proof_of_possession: false,
|
|
129
|
+
}]
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
checkDPoP(options, (result) => {
|
|
133
|
+
expect(result).to.be.an("array").with.lengthOf(1);
|
|
134
|
+
expect(result[0].report).to.be.an("array").with.lengthOf(1);
|
|
135
|
+
expect(result[0].report[0]).to.include({
|
|
136
|
+
field: "require_proof_of_possession",
|
|
137
|
+
status: CONSTANTS.FAIL,
|
|
138
|
+
value: false,
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should report failure if device_code grant is used and Token Sender-Constraining is not enabled", function() {
|
|
144
|
+
const options = {
|
|
145
|
+
clients: [{
|
|
146
|
+
name: "Device App",
|
|
147
|
+
client_id: "client_device",
|
|
148
|
+
app_type: "native",
|
|
149
|
+
is_first_party: true,
|
|
150
|
+
grant_types: ["urn:ietf:params:oauth:grant-type:device_code"],
|
|
151
|
+
require_proof_of_possession: false,
|
|
152
|
+
}]
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
checkDPoP(options, (result) => {
|
|
156
|
+
expect(result).to.be.an("array").with.lengthOf(1);
|
|
157
|
+
expect(result[0].report).to.be.an("array").with.lengthOf(1);
|
|
158
|
+
expect(result[0].report[0]).to.include({
|
|
159
|
+
field: "require_proof_of_possession",
|
|
160
|
+
status: CONSTANTS.FAIL,
|
|
161
|
+
value: false,
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("should not flag clients with only implicit grant type", function() {
|
|
167
|
+
const options = {
|
|
168
|
+
clients: [{
|
|
169
|
+
name: "Legacy SPA",
|
|
170
|
+
client_id: "client_implicit",
|
|
171
|
+
app_type: "spa",
|
|
172
|
+
is_first_party: true,
|
|
173
|
+
grant_types: ["implicit"],
|
|
174
|
+
require_proof_of_possession: false,
|
|
175
|
+
}]
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
checkDPoP(options, (result) => {
|
|
179
|
+
expect(result).to.be.an("array").with.lengthOf(1);
|
|
180
|
+
expect(result[0].report).to.be.an("array").that.is.empty;
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should return empty result if no clients are provided", function() {
|
|
185
|
+
const options = { clients: [] };
|
|
186
|
+
checkDPoP(options, (result) => {
|
|
187
|
+
expect(result).to.be.an("array").that.is.empty;
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const chai = require("chai");
|
|
2
|
+
const expect = chai.expect;
|
|
3
|
+
|
|
4
|
+
const checkAPISigningAlgorithm = require("../../analyzer/lib/resource_servers/checkAPISigningAlgorithm");
|
|
5
|
+
const CONSTANTS = require("../../analyzer/lib/constants");
|
|
6
|
+
|
|
7
|
+
describe("checkAPISigningAlgorithm", function () {
|
|
8
|
+
it("should return an empty report when resourceServers is empty", async function () {
|
|
9
|
+
const options = { resourceServers: [] };
|
|
10
|
+
|
|
11
|
+
const result = await checkAPISigningAlgorithm(options);
|
|
12
|
+
expect(result.details).to.deep.equal([]);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should return an empty report when resourceServers is undefined", async function () {
|
|
16
|
+
const options = { resourceServers: undefined };
|
|
17
|
+
|
|
18
|
+
const result = await checkAPISigningAlgorithm(options);
|
|
19
|
+
expect(result.details).to.deep.equal([]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should skip system APIs (Auth0 Management API)", async function () {
|
|
23
|
+
const options = {
|
|
24
|
+
resourceServers: [
|
|
25
|
+
{
|
|
26
|
+
id: "system_api",
|
|
27
|
+
name: "Auth0 Management API",
|
|
28
|
+
identifier: "https://tenant.auth0.com/api/v2/",
|
|
29
|
+
is_system: true,
|
|
30
|
+
signing_alg: "RS256",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const result = await checkAPISigningAlgorithm(options);
|
|
36
|
+
expect(result.details).to.deep.equal([]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should return fail for API using HS256 signing algorithm", async function () {
|
|
40
|
+
const options = {
|
|
41
|
+
resourceServers: [
|
|
42
|
+
{
|
|
43
|
+
id: "api_1",
|
|
44
|
+
name: "My API",
|
|
45
|
+
identifier: "https://api.example.com",
|
|
46
|
+
is_system: false,
|
|
47
|
+
signing_alg: "HS256",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const result = await checkAPISigningAlgorithm(options);
|
|
53
|
+
const report = result.details[0].report;
|
|
54
|
+
|
|
55
|
+
expect(report).to.have.lengthOf(1);
|
|
56
|
+
expect(report[0].field).to.equal("using_symmetric_alg");
|
|
57
|
+
expect(report[0].status).to.equal(CONSTANTS.FAIL);
|
|
58
|
+
expect(report[0].value).to.equal("HS256");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should return success for API using RS256 signing algorithm", async function () {
|
|
62
|
+
const options = {
|
|
63
|
+
resourceServers: [
|
|
64
|
+
{
|
|
65
|
+
id: "api_1",
|
|
66
|
+
name: "My API",
|
|
67
|
+
identifier: "https://api.example.com",
|
|
68
|
+
is_system: false,
|
|
69
|
+
signing_alg: "RS256",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const result = await checkAPISigningAlgorithm(options);
|
|
75
|
+
const report = result.details[0].report;
|
|
76
|
+
|
|
77
|
+
expect(report).to.have.lengthOf(1);
|
|
78
|
+
expect(report[0].field).to.equal("using_asymmetric_alg");
|
|
79
|
+
expect(report[0].status).to.equal(CONSTANTS.SUCCESS);
|
|
80
|
+
expect(report[0].value).to.equal("RS256");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should default to RS256 when signing_alg is not specified", async function () {
|
|
84
|
+
const options = {
|
|
85
|
+
resourceServers: [
|
|
86
|
+
{
|
|
87
|
+
id: "api_1",
|
|
88
|
+
name: "My API",
|
|
89
|
+
identifier: "https://api.example.com",
|
|
90
|
+
is_system: false,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const result = await checkAPISigningAlgorithm(options);
|
|
96
|
+
const report = result.details[0].report;
|
|
97
|
+
|
|
98
|
+
expect(report[0].field).to.equal("using_asymmetric_alg");
|
|
99
|
+
expect(report[0].status).to.equal(CONSTANTS.SUCCESS);
|
|
100
|
+
expect(report[0].value).to.equal("RS256");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should handle multiple APIs with different signing algorithms", async function () {
|
|
104
|
+
const options = {
|
|
105
|
+
resourceServers: [
|
|
106
|
+
{
|
|
107
|
+
id: "api_1",
|
|
108
|
+
name: "Secure API",
|
|
109
|
+
identifier: "https://secure.api.com",
|
|
110
|
+
is_system: false,
|
|
111
|
+
signing_alg: "RS256",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: "api_2",
|
|
115
|
+
name: "Legacy API",
|
|
116
|
+
identifier: "https://legacy.api.com",
|
|
117
|
+
is_system: false,
|
|
118
|
+
signing_alg: "HS256",
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const result = await checkAPISigningAlgorithm(options);
|
|
124
|
+
expect(result.details).to.have.lengthOf(2);
|
|
125
|
+
|
|
126
|
+
const secureApiReport = result.details.find((d) => d.name.includes("Secure API"));
|
|
127
|
+
expect(secureApiReport.report[0].status).to.equal(CONSTANTS.SUCCESS);
|
|
128
|
+
|
|
129
|
+
const legacyApiReport = result.details.find((d) => d.name.includes("Legacy API"));
|
|
130
|
+
expect(legacyApiReport.report[0].status).to.equal(CONSTANTS.FAIL);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
const chai = require("chai");
|
|
2
|
+
const expect = chai.expect;
|
|
3
|
+
|
|
4
|
+
const checkAPITokenLifetime = require("../../analyzer/lib/resource_servers/checkAPITokenLifetime");
|
|
5
|
+
const CONSTANTS = require("../../analyzer/lib/constants");
|
|
6
|
+
|
|
7
|
+
describe("checkAPITokenLifetime", function () {
|
|
8
|
+
it("should return an empty report when resourceServers is empty", async function () {
|
|
9
|
+
const options = { resourceServers: [] };
|
|
10
|
+
|
|
11
|
+
const result = await checkAPITokenLifetime(options);
|
|
12
|
+
expect(result.details).to.deep.equal([]);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should return an empty report when resourceServers is undefined", async function () {
|
|
16
|
+
const options = { resourceServers: undefined };
|
|
17
|
+
|
|
18
|
+
const result = await checkAPITokenLifetime(options);
|
|
19
|
+
expect(result.details).to.deep.equal([]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should skip system APIs (Auth0 Management API)", async function () {
|
|
23
|
+
const options = {
|
|
24
|
+
resourceServers: [
|
|
25
|
+
{
|
|
26
|
+
id: "system_api",
|
|
27
|
+
name: "Auth0 Management API",
|
|
28
|
+
identifier: "https://tenant.auth0.com/api/v2/",
|
|
29
|
+
is_system: true,
|
|
30
|
+
token_lifetime: 86400,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const result = await checkAPITokenLifetime(options);
|
|
36
|
+
expect(result.details).to.deep.equal([]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should return fail for API with token lifetime >= 7 days", async function () {
|
|
40
|
+
const options = {
|
|
41
|
+
resourceServers: [
|
|
42
|
+
{
|
|
43
|
+
id: "api_1",
|
|
44
|
+
name: "My API",
|
|
45
|
+
identifier: "https://api.example.com",
|
|
46
|
+
is_system: false,
|
|
47
|
+
token_lifetime: 604800, // 7 days
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const result = await checkAPITokenLifetime(options);
|
|
53
|
+
const report = result.details[0].report;
|
|
54
|
+
|
|
55
|
+
expect(report).to.have.lengthOf(1);
|
|
56
|
+
expect(report[0].field).to.equal("token_lifetime_too_long");
|
|
57
|
+
expect(report[0].status).to.equal(CONSTANTS.FAIL);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should return fail for API with token lifetime at maximum (30 days)", async function () {
|
|
61
|
+
const options = {
|
|
62
|
+
resourceServers: [
|
|
63
|
+
{
|
|
64
|
+
id: "api_1",
|
|
65
|
+
name: "My API",
|
|
66
|
+
identifier: "https://api.example.com",
|
|
67
|
+
is_system: false,
|
|
68
|
+
token_lifetime: 2592000, // 30 days (max)
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const result = await checkAPITokenLifetime(options);
|
|
74
|
+
const report = result.details[0].report;
|
|
75
|
+
|
|
76
|
+
expect(report[0].field).to.equal("token_lifetime_too_long");
|
|
77
|
+
expect(report[0].status).to.equal(CONSTANTS.FAIL);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should return warn for API with token lifetime > 24 hours but < 7 days", async function () {
|
|
81
|
+
const options = {
|
|
82
|
+
resourceServers: [
|
|
83
|
+
{
|
|
84
|
+
id: "api_1",
|
|
85
|
+
name: "My API",
|
|
86
|
+
identifier: "https://api.example.com",
|
|
87
|
+
is_system: false,
|
|
88
|
+
token_lifetime: 172800, // 2 days
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const result = await checkAPITokenLifetime(options);
|
|
94
|
+
const report = result.details[0].report;
|
|
95
|
+
|
|
96
|
+
expect(report).to.have.lengthOf(1);
|
|
97
|
+
expect(report[0].field).to.equal("token_lifetime_extended");
|
|
98
|
+
expect(report[0].status).to.equal(CONSTANTS.WARN);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should return success for API with token lifetime <= 24 hours", async function () {
|
|
102
|
+
const options = {
|
|
103
|
+
resourceServers: [
|
|
104
|
+
{
|
|
105
|
+
id: "api_1",
|
|
106
|
+
name: "My API",
|
|
107
|
+
identifier: "https://api.example.com",
|
|
108
|
+
is_system: false,
|
|
109
|
+
token_lifetime: 86400, // 24 hours (default)
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const result = await checkAPITokenLifetime(options);
|
|
115
|
+
const report = result.details[0].report;
|
|
116
|
+
|
|
117
|
+
expect(report).to.have.lengthOf(1);
|
|
118
|
+
expect(report[0].field).to.equal("token_lifetime_appropriate");
|
|
119
|
+
expect(report[0].status).to.equal(CONSTANTS.SUCCESS);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should return success for API with short token lifetime (1 hour)", async function () {
|
|
123
|
+
const options = {
|
|
124
|
+
resourceServers: [
|
|
125
|
+
{
|
|
126
|
+
id: "api_1",
|
|
127
|
+
name: "My API",
|
|
128
|
+
identifier: "https://api.example.com",
|
|
129
|
+
is_system: false,
|
|
130
|
+
token_lifetime: 3600, // 1 hour
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const result = await checkAPITokenLifetime(options);
|
|
136
|
+
const report = result.details[0].report;
|
|
137
|
+
|
|
138
|
+
expect(report[0].field).to.equal("token_lifetime_appropriate");
|
|
139
|
+
expect(report[0].status).to.equal(CONSTANTS.SUCCESS);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("should use default token lifetime (24h) when not specified", async function () {
|
|
143
|
+
const options = {
|
|
144
|
+
resourceServers: [
|
|
145
|
+
{
|
|
146
|
+
id: "api_1",
|
|
147
|
+
name: "My API",
|
|
148
|
+
identifier: "https://api.example.com",
|
|
149
|
+
is_system: false,
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const result = await checkAPITokenLifetime(options);
|
|
155
|
+
const report = result.details[0].report;
|
|
156
|
+
|
|
157
|
+
expect(report[0].field).to.equal("token_lifetime_appropriate");
|
|
158
|
+
expect(report[0].status).to.equal(CONSTANTS.SUCCESS);
|
|
159
|
+
expect(report[0].value).to.include("86400");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should format duration correctly in the value field", async function () {
|
|
163
|
+
const options = {
|
|
164
|
+
resourceServers: [
|
|
165
|
+
{
|
|
166
|
+
id: "api_1",
|
|
167
|
+
name: "My API",
|
|
168
|
+
identifier: "https://api.example.com",
|
|
169
|
+
is_system: false,
|
|
170
|
+
token_lifetime: 172800, // 2 days
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const result = await checkAPITokenLifetime(options);
|
|
176
|
+
const report = result.details[0].report;
|
|
177
|
+
|
|
178
|
+
expect(report[0].value).to.include("2 days");
|
|
179
|
+
expect(report[0].value).to.include("172800 seconds");
|
|
180
|
+
});
|
|
181
|
+
});
|