sfdx-hardis 7.15.0 → 7.16.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 (54) hide show
  1. package/lib/commands/hardis/org/diagnose/mfa.d.ts +14 -0
  2. package/lib/commands/hardis/org/diagnose/mfa.js +261 -0
  3. package/lib/commands/hardis/org/diagnose/mfa.js.map +1 -0
  4. package/lib/commands/hardis/org/user/unlink-security-key.d.ts +12 -0
  5. package/lib/commands/hardis/org/user/unlink-security-key.js +248 -0
  6. package/lib/commands/hardis/org/user/unlink-security-key.js.map +1 -0
  7. package/lib/common/gitProvider/azureDevops.js +7 -4
  8. package/lib/common/gitProvider/azureDevops.js.map +1 -1
  9. package/lib/common/gitProvider/bitbucket.js +7 -4
  10. package/lib/common/gitProvider/bitbucket.js.map +1 -1
  11. package/lib/common/gitProvider/github.js +7 -4
  12. package/lib/common/gitProvider/github.js.map +1 -1
  13. package/lib/common/gitProvider/gitlab.js +7 -4
  14. package/lib/common/gitProvider/gitlab.js.map +1 -1
  15. package/lib/common/metadata-utils/metadataList.js +21 -0
  16. package/lib/common/metadata-utils/metadataList.js.map +1 -1
  17. package/lib/common/monitoring/monitoringDefaults.js +8 -0
  18. package/lib/common/monitoring/monitoringDefaults.js.map +1 -1
  19. package/lib/common/notifProvider/emailProvider.js +12 -2
  20. package/lib/common/notifProvider/emailProvider.js.map +1 -1
  21. package/lib/common/notifProvider/types.d.ts +1 -1
  22. package/lib/common/notifProvider/types.js +7 -0
  23. package/lib/common/notifProvider/types.js.map +1 -1
  24. package/lib/common/utils/authUtils.d.ts +1 -0
  25. package/lib/common/utils/authUtils.js +19 -0
  26. package/lib/common/utils/authUtils.js.map +1 -1
  27. package/lib/common/utils/emailUtils.d.ts +6 -0
  28. package/lib/common/utils/emailUtils.js +64 -0
  29. package/lib/common/utils/emailUtils.js.map +1 -1
  30. package/lib/common/utils/mfaDiagnoseUtils.d.ts +51 -0
  31. package/lib/common/utils/mfaDiagnoseUtils.js +743 -0
  32. package/lib/common/utils/mfaDiagnoseUtils.js.map +1 -0
  33. package/lib/common/utils/orgUserUnlinkUtils.d.ts +28 -0
  34. package/lib/common/utils/orgUserUnlinkUtils.js +445 -0
  35. package/lib/common/utils/orgUserUnlinkUtils.js.map +1 -0
  36. package/lib/common/utils/refresh/externalClientAppUtils.d.ts +13 -5
  37. package/lib/common/utils/refresh/externalClientAppUtils.js +76 -29
  38. package/lib/common/utils/refresh/externalClientAppUtils.js.map +1 -1
  39. package/lib/hooks/auth/auth.js +17 -14
  40. package/lib/hooks/auth/auth.js.map +1 -1
  41. package/lib/hooks/prerun/auth.js +17 -14
  42. package/lib/hooks/prerun/auth.js.map +1 -1
  43. package/lib/i18n/de.json +130 -28
  44. package/lib/i18n/en.json +133 -31
  45. package/lib/i18n/es.json +130 -28
  46. package/lib/i18n/fr.json +131 -29
  47. package/lib/i18n/it.json +132 -30
  48. package/lib/i18n/ja.json +132 -30
  49. package/lib/i18n/nl.json +131 -29
  50. package/lib/i18n/pl.json +121 -19
  51. package/lib/i18n/pt-BR.json +123 -21
  52. package/oclif.lock +429 -366
  53. package/oclif.manifest.json +7776 -7465
  54. package/package.json +9 -7
@@ -0,0 +1,14 @@
1
+ import { SfCommand } from '@salesforce/sf-plugins-core';
2
+ import { AnyJson } from '@salesforce/ts-types';
3
+ export default class DiagnoseMfa extends SfCommand<any> {
4
+ static title: string;
5
+ static description: string;
6
+ static examples: string[];
7
+ static flags: any;
8
+ static requiresProject: boolean;
9
+ protected static triggerNotification: boolean;
10
+ protected debugMode: boolean;
11
+ protected outputFile: any;
12
+ protected outputFilesRes: any;
13
+ run(): Promise<AnyJson>;
14
+ }
@@ -0,0 +1,261 @@
1
+ /* jscpd:ignore-start */
2
+ import { SfCommand, Flags, requiredOrgFlagWithDeprecations } from '@salesforce/sf-plugins-core';
3
+ import { Messages } from '@salesforce/core';
4
+ import c from 'chalk';
5
+ import { uxLog, uxLogTable } from '../../../../common/utils/index.js';
6
+ import { generateCsvFile, generateReportPath } from '../../../../common/utils/filesUtils.js';
7
+ import { getNotificationButtons, getOrgMarkdown } from '../../../../common/utils/notifUtils.js';
8
+ import { NotifProvider } from '../../../../common/notifProvider/index.js';
9
+ import { setConnectionVariables } from '../../../../common/utils/orgUtils.js';
10
+ import { CONSTANTS, getConfig, getEnvVarList } from '../../../../config/index.js';
11
+ import { DEFAULT_PRIVILEGED_PERM_FIELDS, diagnoseMfa, } from '../../../../common/utils/mfaDiagnoseUtils.js';
12
+ import { t } from '../../../../common/utils/i18n.js';
13
+ Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
14
+ const messages = Messages.loadMessages('sfdx-hardis', 'org');
15
+ export default class DiagnoseMfa extends SfCommand {
16
+ static title = 'Diagnose MFA configuration';
17
+ static description = `
18
+ ## Command Behavior
19
+
20
+ **Audits the org's Multi-Factor Authentication (MFA) configuration and reports gaps that violate the Salesforce MFA requirement.**
21
+
22
+ This monitoring command runs five independent checks against the target org and emits a single \`MFA_CONFIG\` notification with the consolidated findings.
23
+
24
+ Key checks:
25
+
26
+ - **Org-wide MFA enforcement.** Reads \`SecuritySettings.Metadata.sessionSettings\` via the Tooling API to inspect the \`enableMFADirectUILoginOptIn\` and \`skipSFAWhenMFADirectUILogin\` flags, and scans recent \`LoginHistory\` for at least one \`Status = 'Multi-factor required'\` event to detect platform-level enforcement. Weak identity methods (\`enableSMSIdentity\`, \`canConfirmIdentityBySmsOnly\`) downgrade the result to a warning.
27
+ - **Users with the MFA-bypass permission.** Queries Profiles and Permission Sets where \`PermissionsBypassMFAForUiLogins = true\` and lists every active Standard user assigned to them.
28
+ - **Privileged users coverage.** For users assigned to any Profile or Permission Set granting \`ModifyAllData\`, \`ViewAllData\`, \`CustomizeApplication\`, or \`AuthorApex\` (configurable), the report shows whether each privileged user also holds the MFA-bypass permission (error) or lacks the API MFA permission \`PermissionsTwoFactorApi\` (warning).
29
+ - **SSO presence.** Detects SAML SSO via \`singleSignOnSettings.enableSamlLogin\` in the same SecuritySettings metadata. When SSO is on the report adds an informational reminder to verify MFA at the IdP, which sfdx-hardis cannot introspect.
30
+ - **Non-MFA direct UI logins.** Scans \`LoginHistory\` over the configurable lookback window (default 30 days) for \`LoginType = 'Application'\` successes whose \`AuthMethodReference\` contains no strong-auth token (\`mfa\`, \`swk\`, \`fido\`, \`wia\`, \`hwk\`, \`face\`, \`fpt\`, \`otp\`). Records with a null \`AuthMethodReference\` are skipped (genuinely unknown), so this check only fires on confirmed non-MFA sessions.
31
+
32
+ The severity rollup is:
33
+
34
+ - **error** if org-wide MFA enforcement is missing, any privileged user has the bypass permission, or any non-MFA direct UI login was detected.
35
+ - **warning** if any other finding is present (non-privileged bypass user, privileged users missing API MFA, weak identity setting, or SSO is enabled without an asserted IdP MFA policy).
36
+ - **log** when every check passes.
37
+
38
+ Exclusions:
39
+
40
+ - Users listed in the project config key \`monitoringMfaIgnoreUsers\` or the env var \`MONITORING_MFA_IGNORE_USERS\` (comma-separated) are skipped from checks #2, #3 (only for ignore-overlapping cases) and #5.
41
+ - Users with \`UserType != 'Standard'\` (integration users, Chatter Only, etc.) are not flagged in any per-user check.
42
+
43
+ This command is part of [sfdx-hardis Monitoring](${CONSTANTS.DOC_URL_ROOT}/salesforce-monitoring-home/) and produces Grafana, Slack, Microsoft Teams, Google Chat, and email notifications.
44
+
45
+ ### Agent Mode
46
+
47
+ Supports non-interactive execution with \`--agent\`:
48
+
49
+ \`\`\`sh
50
+ sf hardis:org:diagnose:mfa --agent --target-org myorg@example.com
51
+ \`\`\`
52
+
53
+ In agent mode the command is fully non-interactive (there is no prompt in the happy path); the flag exists for consistency with the rest of the monitoring suite.
54
+
55
+ <details markdown="1">
56
+ <summary>Technical explanations</summary>
57
+
58
+ The command's implementation:
59
+
60
+ - Tooling API: \`SELECT Id, Metadata FROM SecuritySettings LIMIT 1\` to retrieve session and SSO settings.
61
+ - SOQL: \`PermissionSet\` and \`Profile\` filtered on \`PermissionsBypassMFAForUiLogins\`, \`PermissionsTwoFactorApi\`, and the configured privileged permission fields; \`PermissionSetAssignment\` joined with \`User\` to resolve assignees; \`User\` filtered by \`ProfileId\` for profile-based grants.
62
+ - SOQL: \`LoginHistory\` over \`LAST_N_DAYS:N\` with client-side filtering on \`Status\`, \`LoginType\` and \`AuthMethodReference\` because \`Status\` cannot be filtered in a WHERE clause.
63
+ - TwoFactorMethodsInfo is intentionally not queried: Salesforce does not allow runtime SOQL access to it from a CLI session, so per-user method registration cannot be introspected.
64
+ - Reports: a single CSV / XLSX (\`mfa-config-<date>\`) listing every check row and a per-check summary table in the console.
65
+
66
+ Reference: [Salesforce MFA Requirement](https://help.salesforce.com/s/articleView?id=005321563&type=1).
67
+ </details>
68
+ `;
69
+ static examples = [
70
+ '$ sf hardis:org:diagnose:mfa',
71
+ '$ sf hardis:org:diagnose:mfa --target-org myorg@example.com',
72
+ '$ sf hardis:org:diagnose:mfa --lookback-days 60',
73
+ "$ sf hardis:org:diagnose:mfa --ignore-users 'integration@x.com,break-glass@x.com'",
74
+ '$ sf hardis:org:diagnose:mfa --agent',
75
+ ];
76
+ static flags = {
77
+ 'lookback-days': Flags.integer({
78
+ description: 'Number of days back to scan LoginHistory for non-MFA direct UI logins. Overrides monitoringMfaLoginHistoryLookbackDays from config.',
79
+ }),
80
+ 'phishing-resistant-lookback-days': Flags.integer({
81
+ description: 'Number of days back to scan VerificationHistory for phishing-resistant MFA registration / usage per privileged user. Overrides monitoringMfaPhishingResistantLookbackDays from config (default: 180).',
82
+ }),
83
+ 'ignore-users': Flags.string({
84
+ description: 'Comma-separated list of usernames to exclude from MFA checks (merged with monitoringMfaIgnoreUsers config and MONITORING_MFA_IGNORE_USERS env var).',
85
+ }),
86
+ 'privileged-permissions': Flags.string({
87
+ description: 'Comma-separated list of PermissionSet/Profile permission API names that define a privileged user. Defaults to PermissionsModifyAllData,PermissionsViewAllData,PermissionsCustomizeApplication,PermissionsAuthorApex.',
88
+ }),
89
+ agent: Flags.boolean({
90
+ default: false,
91
+ description: 'Run in non-interactive mode for agents and automation',
92
+ }),
93
+ debug: Flags.boolean({
94
+ char: 'd',
95
+ default: false,
96
+ description: messages.getMessage('debugMode'),
97
+ }),
98
+ websocket: Flags.string({
99
+ description: messages.getMessage('websocket'),
100
+ }),
101
+ skipauth: Flags.boolean({
102
+ description: 'Skip authentication check when a default username is required',
103
+ }),
104
+ 'target-org': requiredOrgFlagWithDeprecations,
105
+ };
106
+ static requiresProject = false;
107
+ static triggerNotification = true;
108
+ debugMode = false;
109
+ outputFile;
110
+ outputFilesRes = {};
111
+ /* jscpd:ignore-end */
112
+ async run() {
113
+ const { flags } = await this.parse(DiagnoseMfa);
114
+ this.debugMode = flags.debug || false;
115
+ const conn = flags['target-org'].getConnection();
116
+ const config = await getConfig('project');
117
+ const lookbackDays = flags['lookback-days'] ?? config.monitoringMfaLoginHistoryLookbackDays ?? 30;
118
+ const phishingResistantLookbackDays = flags['phishing-resistant-lookback-days'] ?? config.monitoringMfaPhishingResistantLookbackDays ?? 180;
119
+ const ignoreFromFlag = flags['ignore-users']
120
+ ? flags['ignore-users'].split(',').map((u) => u.trim()).filter(Boolean)
121
+ : [];
122
+ const ignoreFromEnv = getEnvVarList('MONITORING_MFA_IGNORE_USERS') || [];
123
+ const ignoreFromConfig = config.monitoringMfaIgnoreUsers || [];
124
+ const ignoreUsers = Array.from(new Set([...ignoreFromFlag, ...ignoreFromEnv, ...ignoreFromConfig]));
125
+ const privilegedPermFields = flags['privileged-permissions']
126
+ ? flags['privileged-permissions'].split(',').map((p) => p.trim()).filter(Boolean)
127
+ : DEFAULT_PRIVILEGED_PERM_FIELDS;
128
+ if (ignoreUsers.length > 0) {
129
+ uxLog('action', this, c.yellow(t('mfaIgnoreListInUse', { count: ignoreUsers.length })));
130
+ for (const u of ignoreUsers) {
131
+ uxLog('log', this, `- ${u}`);
132
+ }
133
+ }
134
+ uxLog('action', this, c.cyan(t('mfaQueryingSecuritySettings')));
135
+ uxLog('action', this, c.cyan(t('mfaQueryingPermissionEntities')));
136
+ uxLog('action', this, c.cyan(t('mfaQueryingLoginHistory', { days: lookbackDays })));
137
+ const result = await diagnoseMfa({
138
+ conn,
139
+ lookbackDays,
140
+ phishingResistantLookbackDays,
141
+ ignoreUsers,
142
+ privilegedPermFields,
143
+ debug: this.debugMode,
144
+ });
145
+ /* Per-check detail tables - only print rows that have a finding so the console stays readable */
146
+ for (const ck of result.checks) {
147
+ const findingRows = ck.rows.filter((r) => r.Severity !== 'success');
148
+ if (findingRows.length === 0)
149
+ continue;
150
+ uxLog('action', this, c.cyan(`${ck.title} (${ck.status})`));
151
+ uxLogTable(this, findingRows.map((r) => ({
152
+ Item: r.Item,
153
+ 'What is wrong': r.Details,
154
+ Recommendation: r.Recommendation,
155
+ })));
156
+ }
157
+ /* Generate report file from the unified-column rows - one row per finding */
158
+ this.outputFile = await generateReportPath('mfa-config', this.outputFile);
159
+ this.outputFilesRes = await generateCsvFile(result.reportRows, this.outputFile, {
160
+ fileTitle: t('mfaConfigReportFileTitle'),
161
+ });
162
+ /* Summary section: per-check overview table, then Actions to take as plain lines */
163
+ uxLog('action', this, c.cyan(t('mfaSummaryHeader')));
164
+ const severityIcon = {
165
+ success: '✅',
166
+ info: 'ℹ️',
167
+ warning: '⚠️',
168
+ error: '❌',
169
+ };
170
+ const summaryRows = result.checks.map((ck) => ({
171
+ Check: ck.title,
172
+ Severity: `${severityIcon[ck.status] || ''} ${ck.status}`.trim(),
173
+ Detail: ck.detail,
174
+ Findings: ck.metric,
175
+ }));
176
+ uxLogTable(this, summaryRows);
177
+ if (result.actionItems.length === 0) {
178
+ uxLog('success', this, c.green(t('mfaActionItemsNone')));
179
+ }
180
+ else {
181
+ /* Sub-heading inside the Summary section - not a new "action" log so we don't break the section grouping */
182
+ uxLog('log', this, c.cyan.bold(t('mfaActionItemsHeader')));
183
+ for (const group of result.actionItems) {
184
+ const sevLabel = t(group.severity === 'error'
185
+ ? 'mfaSeverityError'
186
+ : group.severity === 'warning'
187
+ ? 'mfaSeverityWarning'
188
+ : 'mfaSeverityInfo');
189
+ const colorFn = group.severity === 'error' ? c.red : group.severity === 'warning' ? c.yellow : c.cyan;
190
+ const heads = group.items.map((it) => it.split(' - ')[0]);
191
+ const preview = heads.slice(0, 5).join(', ');
192
+ const more = heads.length > 5 ? `, +${heads.length - 5} ${t('mfaActionItemsMore')}` : '';
193
+ uxLog('warning', this, colorFn(`[${sevLabel}] (${heads.length}) ${group.recommendation}`));
194
+ uxLog('log', this, c.grey(` ${t('mfaActionItemsAffected')}: ${preview}${more}`));
195
+ }
196
+ uxLog('log', this, c.grey(t('mfaActionItemsSeeCsv')));
197
+ }
198
+ /* Final summary line */
199
+ if (result.severity === 'log') {
200
+ uxLog('success', this, c.green(t('mfaSummaryOk')));
201
+ }
202
+ else if (result.severity === 'warning') {
203
+ uxLog('warning', this, c.yellow(t('mfaSummaryWarning')));
204
+ }
205
+ else {
206
+ uxLog('error', this, c.red(t('mfaSummaryError')));
207
+ }
208
+ /* Build notification */
209
+ const orgMarkdown = await getOrgMarkdown(conn?.instanceUrl);
210
+ const notifButtons = await getNotificationButtons();
211
+ const notifSeverity = result.severity;
212
+ const checksFailed = result.checks.filter((ck) => ck.status === 'error' || ck.status === 'warning').length;
213
+ const notifText = t('mfaNotificationText', {
214
+ orgMarkdown,
215
+ checksFailed,
216
+ nonMfaLogins: result.metrics.NonMfaLogins,
217
+ bypassUsers: result.metrics.MfaBypassUsers,
218
+ });
219
+ /*
220
+ * Notification body is intentionally terse: one line per failing check, nothing more.
221
+ * Passing checks are omitted (the headline already says "X check(s) flagged"). The full
222
+ * recommendations, affected-user lists, and action items live in the attached XLSX report.
223
+ */
224
+ const notifIcon = { success: '✅', info: 'ℹ️', warning: '⚠️', error: '❌' };
225
+ const failingChecks = result.checks.filter((ck) => ck.status === 'error' || ck.status === 'warning');
226
+ let notifDetailText = '';
227
+ for (const ck of failingChecks) {
228
+ notifDetailText += `${notifIcon[ck.status] || ''} **${ck.title}**: ${ck.detail}\n`;
229
+ }
230
+ if (failingChecks.length === 0) {
231
+ notifDetailText = t('mfaSummaryOk');
232
+ }
233
+ const notifAttachments = [{ text: notifDetailText }];
234
+ await setConnectionVariables(conn);
235
+ await NotifProvider.postNotifications({
236
+ type: 'MFA_CONFIG',
237
+ text: notifText,
238
+ attachments: notifAttachments,
239
+ buttons: notifButtons,
240
+ severity: notifSeverity,
241
+ attachedFiles: this.outputFilesRes.xlsxFile ? [this.outputFilesRes.xlsxFile] : [],
242
+ logElements: result.reportRows,
243
+ data: { metric: checksFailed },
244
+ metrics: result.metrics,
245
+ });
246
+ return {
247
+ orgId: flags['target-org'].getOrgId(),
248
+ severity: result.severity,
249
+ metrics: result.metrics,
250
+ checks: result.checks.map((ck) => ({
251
+ key: ck.key,
252
+ status: ck.status,
253
+ title: ck.title,
254
+ detail: ck.detail,
255
+ metric: ck.metric,
256
+ })),
257
+ outputString: notifText,
258
+ };
259
+ }
260
+ }
261
+ //# sourceMappingURL=mfa.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mfa.js","sourceRoot":"","sources":["../../../../../src/commands/hardis/org/diagnose/mfa.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { SfCommand } from '@salesforce/sf-plugins-core';
2
+ import { AnyJson } from '@salesforce/ts-types';
3
+ export default class OrgUserUnlinkSecurityKey extends SfCommand<any> {
4
+ static title: string;
5
+ static description: string;
6
+ static examples: string[];
7
+ static flags: any;
8
+ static requiresProject: boolean;
9
+ protected debugMode: boolean;
10
+ protected agentMode: boolean;
11
+ run(): Promise<AnyJson>;
12
+ }
@@ -0,0 +1,248 @@
1
+ /* jscpd:ignore-start */
2
+ import { SfCommand, Flags, requiredOrgFlagWithDeprecations } from '@salesforce/sf-plugins-core';
3
+ import { Messages, SfError } from '@salesforce/core';
4
+ import c from 'chalk';
5
+ import { generateReports, isCI, uxLog, uxLogTable } from '../../../../common/utils/index.js';
6
+ import { prompts } from '../../../../common/utils/prompts.js';
7
+ import { soqlQuery } from '../../../../common/utils/apiUtils.js';
8
+ import { MFA_METHODS, unlinkUserSecurityKeys, } from '../../../../common/utils/orgUserUnlinkUtils.js';
9
+ import { t } from '../../../../common/utils/i18n.js';
10
+ Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
11
+ const messages = Messages.loadMessages('sfdx-hardis', 'org');
12
+ export default class OrgUserUnlinkSecurityKey extends SfCommand {
13
+ static title = 'Unlink user security keys / MFA methods';
14
+ static description = `
15
+ ## Command Behavior
16
+
17
+ **Disconnects MFA registrations (default: Security Key / U2F + WebAuthn) from selected Salesforce users by driving a real browser session against Salesforce Setup.**
18
+
19
+ This command automates the manual admin procedure documented at https://help.salesforce.com/s/articleView?id=xcloud.security_u2f_remove_users_security_key.htm so administrators can revoke a lost or compromised security key for multiple users at once.
20
+
21
+ Key functionalities:
22
+
23
+ - **User selection:** Provide a comma-separated list of usernames via \`--usernames\`. In interactive mode, you will be prompted if the flag is missing.
24
+ - **Method selection (opt-in):** Security Key (U2F + WebAuthn combined) is removed by default. Use \`--include-salesforce-authenticator\` or \`--include-totp\` to also disconnect those methods. Use \`--no-include-security-key\` to skip the security-key removal when combining with other methods.
25
+ - **Inactive users are skipped:** Inactive users (\`IsActive = false\`) are reported with status \`inactive\` and no browser action is attempted for them.
26
+ - **Per-user result:** Each username receives one of the following statuses: \`unlinked\`, \`notLinked\`, \`notFound\`, \`inactive\`, or \`error\`.
27
+ - **Locale-independent detection:** Disconnect links are located by the brand / spec tokens (\`U2F\`, \`WebAuthn\`, \`Salesforce Authenticator\`, \`One-Time Password Authenticator\`, \`TOTP\`) that Salesforce does not translate. On non-English orgs, you can extend the matching set with \`--text-markers\`.
28
+ - **Reporting:** A console table summarises the run, and CSV / XLSX reports are generated for audit trails.
29
+
30
+ ### Agent Mode
31
+
32
+ Supports non-interactive execution with \`--agent\`:
33
+
34
+ \`\`\`sh
35
+ sf hardis:org:user:unlink-security-key --agent --usernames 'a@x.com,b@x.com' --target-org my-admin@myorg.com
36
+ \`\`\`
37
+
38
+ In agent mode:
39
+
40
+ - The confirmation prompt is skipped and the unlink procedure starts immediately.
41
+ - You must provide \`--usernames\` (the interactive prompt is not available).
42
+ - Method selection flags default to Security Key only unless you opt in.
43
+
44
+ <details markdown="1">
45
+ <summary>Technical explanations</summary>
46
+
47
+ The command's technical implementation involves:
48
+
49
+ - **SOQL Pre-query:** Runs \`SELECT Id, Username, IsActive, Name FROM User WHERE Username IN (...)\` to resolve each username to a User Id. Missing rows produce \`notFound\`; inactive rows are short-circuited to \`inactive\` without launching the browser.
50
+ - **Puppeteer automation:** Reuses \`puppeteer-core\` with the chrome path resolved by \`getChromeExecutablePath()\` and logs in to the org via \`secur/frontdoor.jsp?sid=<accessToken>\`, identical to the existing \`hardis:org:fix:listviewmine\` flow.
51
+ - **Locale-independent row matching:** For each MFA method the command navigates to the user detail page (\`/lightning/setup/ManageUsers/page?address=%2F<USER_ID>%3Fnoredirect%3D1%26isUserEntityOverride%3D1\`), waits for the inner Classic-style iframe, scans rows for a brand / spec marker (\`U2F\`, \`WebAuthn\`, \`Salesforce Authenticator\`, \`One-Time Password Authenticator\`, \`TOTP\`, etc.) and clicks the single link in that row. URL-based \`href\` patterns are used as a fallback if no row marker is found.
52
+ - **Salesforce labels reference:** The verbatim Setup labels are \`Security Key (U2F or WebAuthn)\` (Delete link), \`App Registration: Salesforce Authenticator\` (Disconnect), \`App Registration: One-Time Password Authenticator\` (Disconnect).
53
+ - **Reporting:** Generates CSV and XLSX files (\`users-security-key-unlink-<date>\`) via \`generateReports\` and prints a console table via \`uxLogTable\`.
54
+
55
+ Reference: [Salesforce Help - Remove a user's security key](https://help.salesforce.com/s/articleView?id=xcloud.security_u2f_remove_users_security_key.htm).
56
+ </details>
57
+ `;
58
+ static examples = [
59
+ "$ sf hardis:org:user:unlink-security-key",
60
+ "$ sf hardis:org:user:unlink-security-key --usernames 'a@x.com,b@x.com'",
61
+ "$ sf hardis:org:user:unlink-security-key --usernames 'a@x.com' --include-salesforce-authenticator --include-totp",
62
+ "$ sf hardis:org:user:unlink-security-key --usernames 'a@x.com' --no-include-security-key --include-totp",
63
+ "$ sf hardis:org:user:unlink-security-key --agent --usernames 'a@x.com,b@x.com'",
64
+ "$ sf hardis:org:user:unlink-security-key --usernames 'a@x.com' --text-markers '{\"salesforceAuthenticator\":[\"Authentificateur Salesforce\"]}'",
65
+ ];
66
+ static flags = {
67
+ usernames: Flags.string({
68
+ char: 'u',
69
+ description: 'Comma-separated list of Salesforce usernames whose security keys should be unlinked',
70
+ }),
71
+ 'include-security-key': Flags.boolean({
72
+ default: true,
73
+ allowNo: true,
74
+ description: 'Unlink the Security Key (U2F + WebAuthn combined) registration (default: true). Use --no-include-security-key to skip.',
75
+ }),
76
+ 'include-salesforce-authenticator': Flags.boolean({
77
+ default: false,
78
+ description: 'Also unlink the Salesforce Authenticator mobile app registration',
79
+ }),
80
+ 'include-totp': Flags.boolean({
81
+ default: false,
82
+ description: 'Also unlink the One-Time Password Authenticator (TOTP) registration',
83
+ }),
84
+ 'text-markers': Flags.string({
85
+ description: 'JSON object adding text markers per method, useful on non-English orgs. Example: --text-markers \'{"salesforceAuthenticator":["Authentificateur Salesforce"]}\'',
86
+ }),
87
+ 'dump-anchors': Flags.boolean({
88
+ default: false,
89
+ description: 'Diagnostic: print every anchor found on each user detail page (use with --debug to refine markers)',
90
+ }),
91
+ agent: Flags.boolean({
92
+ default: false,
93
+ description: 'Run in non-interactive mode for agents and automation',
94
+ }),
95
+ debug: Flags.boolean({
96
+ char: 'd',
97
+ default: false,
98
+ description: messages.getMessage('debugMode'),
99
+ }),
100
+ websocket: Flags.string({
101
+ description: messages.getMessage('websocket'),
102
+ }),
103
+ skipauth: Flags.boolean({
104
+ description: 'Skip authentication check when a default username is required',
105
+ }),
106
+ 'target-org': requiredOrgFlagWithDeprecations,
107
+ };
108
+ static requiresProject = false;
109
+ debugMode = false;
110
+ agentMode = false;
111
+ /* jscpd:ignore-end */
112
+ async run() {
113
+ const { flags } = await this.parse(OrgUserUnlinkSecurityKey);
114
+ this.debugMode = flags.debug || false;
115
+ this.agentMode = flags.agent === true;
116
+ let usernameList = flags.usernames
117
+ ? flags.usernames.split(',').map((u) => u.trim()).filter((u) => u)
118
+ : [];
119
+ if (usernameList.length === 0) {
120
+ if (isCI || this.agentMode) {
121
+ throw new SfError(c.red('In agent/CI mode, the --usernames flag is required for unlink-security-key operation.'));
122
+ }
123
+ const promptRes = await prompts({
124
+ type: 'text',
125
+ name: 'value',
126
+ message: t('enterUsernamesToUnlink'),
127
+ description: t('unlinkSecurityKeyConfirmDescription'),
128
+ });
129
+ usernameList = (promptRes.value || '')
130
+ .split(',')
131
+ .map((u) => u.trim())
132
+ .filter((u) => u);
133
+ if (usernameList.length === 0) {
134
+ const outputString = 'No usernames provided. Script cancelled.';
135
+ uxLog('warning', this, c.yellow(outputString));
136
+ return { outputString };
137
+ }
138
+ }
139
+ /* Parse optional --text-markers override (extra markers merged per method) */
140
+ let extraMarkers = {};
141
+ if (flags['text-markers']) {
142
+ try {
143
+ const parsed = JSON.parse(flags['text-markers']);
144
+ if (parsed && typeof parsed === 'object') {
145
+ extraMarkers = parsed;
146
+ }
147
+ }
148
+ catch (e) {
149
+ throw new SfError(c.red(`Invalid --text-markers JSON: ${e.message}`));
150
+ }
151
+ }
152
+ const requestedMethodKeys = [];
153
+ if (flags['include-security-key'])
154
+ requestedMethodKeys.push('securityKey');
155
+ if (flags['include-salesforce-authenticator'])
156
+ requestedMethodKeys.push('salesforceAuthenticator');
157
+ if (flags['include-totp'])
158
+ requestedMethodKeys.push('totp');
159
+ if (requestedMethodKeys.length === 0) {
160
+ throw new SfError(c.red('At least one MFA method must be selected (use --include-security-key or one of the --include-* flags).'));
161
+ }
162
+ const methods = requestedMethodKeys.map((k) => {
163
+ const base = MFA_METHODS[k];
164
+ const extras = extraMarkers[k] || [];
165
+ return extras.length > 0
166
+ ? { ...base, sectionTextMarkers: [...base.sectionTextMarkers, ...extras] }
167
+ : base;
168
+ });
169
+ const conn = flags['target-org'].getConnection();
170
+ uxLog('action', this, c.cyan(t('unlinkSecurityKeyQueryingUsers', { count: c.bold(usernameList.length) })));
171
+ const usernamesConstraint = usernameList.map((u) => `'${u}'`).join(',');
172
+ const userQuery = `SELECT Id,Name,Username,IsActive FROM User WHERE Username IN (${usernamesConstraint})`;
173
+ const userQueryRes = await soqlQuery(userQuery, conn);
174
+ const foundByUsername = {};
175
+ for (const rec of userQueryRes.records) {
176
+ foundByUsername[rec.Username] = rec;
177
+ }
178
+ const targets = usernameList.map((username) => {
179
+ const rec = foundByUsername[username];
180
+ if (!rec) {
181
+ return { username, userId: null, isActive: false, preStatus: 'notFound' };
182
+ }
183
+ if (rec.IsActive === false) {
184
+ return { username, userId: rec.Id, isActive: false, name: rec.Name, preStatus: 'inactive' };
185
+ }
186
+ return { username, userId: rec.Id, isActive: true, name: rec.Name, preStatus: 'pending' };
187
+ });
188
+ uxLogTable(this, targets.map((t2) => ({
189
+ Username: t2.username,
190
+ UserId: t2.userId || '',
191
+ IsActive: t2.isActive ? 'true' : 'false',
192
+ PreStatus: t2.preStatus,
193
+ })));
194
+ if (!isCI && !this.agentMode) {
195
+ const confirmRes = await prompts({
196
+ type: 'confirm',
197
+ name: 'value',
198
+ initial: true,
199
+ message: c.cyanBright(t('confirmUnlinkSecurityKey', {
200
+ count: targets.length,
201
+ orgUsername: flags['target-org'].getUsername(),
202
+ })),
203
+ description: t('unlinkSecurityKeyConfirmDescription'),
204
+ });
205
+ if (confirmRes.value !== true) {
206
+ const outputString = 'Script cancelled by user.';
207
+ uxLog('warning', this, c.yellow(outputString));
208
+ return { outputString };
209
+ }
210
+ }
211
+ uxLog('action', this, c.cyan(t('unlinkSecurityKeyLaunchingBrowser')));
212
+ const results = await unlinkUserSecurityKeys(targets, conn, methods, {
213
+ debug: this.debugMode,
214
+ dumpAnchors: flags['dump-anchors'] === true,
215
+ });
216
+ const summary = {
217
+ unlinked: 0,
218
+ notLinked: 0,
219
+ notFound: 0,
220
+ inactive: 0,
221
+ error: 0,
222
+ };
223
+ for (const r of results) {
224
+ summary[r.status] = (summary[r.status] || 0) + 1;
225
+ }
226
+ const tableRows = results.map((r) => ({
227
+ Username: r.username,
228
+ UserId: r.userId || '',
229
+ Status: r.status,
230
+ Unlinked: r.methodsUnlinked.join(', '),
231
+ NotLinked: r.methodsNotLinked.join(', '),
232
+ Message: r.message,
233
+ }));
234
+ uxLogTable(this, tableRows);
235
+ uxLog('success', this, c.green(t('unlinkSecurityKeySummary', summary)));
236
+ await generateReports(tableRows, ['Username', 'UserId', 'Status', 'Unlinked', 'NotLinked', 'Message'], this, {
237
+ logFileName: 'users-security-key-unlink',
238
+ logLabel: 'Unlink security key report',
239
+ });
240
+ return {
241
+ orgId: flags['target-org'].getOrgId(),
242
+ results: results,
243
+ summary,
244
+ outputString: t('unlinkSecurityKeySummary', summary),
245
+ };
246
+ }
247
+ }
248
+ //# sourceMappingURL=unlink-security-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unlink-security-key.js","sourceRoot":"","sources":["../../../../../src/commands/hardis/org/user/unlink-security-key.ts"],"names":[],"mappings":"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"}
@@ -99,10 +99,13 @@ export class AzureDevopsProvider extends GitProviderRoot {
99
99
  }
100
100
  uxLog("log", AzureDevopsProvider, c.grey("[Azure DevOps] " + t("autoDetectProviderJenkinsMapping", { provider: "Azure DevOps" })));
101
101
  }
102
- uxLog("log", AzureDevopsProvider, c.grey("[Azure DevOps] " + t("autoDetectProviderSuccess", {
103
- provider: "Azure DevOps",
104
- details: `server=${process.env.SYSTEM_COLLECTIONURI}, project=${process.env.SYSTEM_TEAMPROJECT || "unknown"}`,
105
- })));
102
+ /* Only log the success summary when Jenkins is involved - on native CI providers this is just noise */
103
+ if (isJenkins()) {
104
+ uxLog("log", AzureDevopsProvider, c.grey("[Azure DevOps] " + t("autoDetectProviderSuccess", {
105
+ provider: "Azure DevOps",
106
+ details: `server=${process.env.SYSTEM_COLLECTIONURI}, project=${process.env.SYSTEM_TEAMPROJECT || "unknown"}`,
107
+ })));
108
+ }
106
109
  }
107
110
  catch (e) {
108
111
  uxLog("warning", AzureDevopsProvider, c.yellow("[Azure DevOps] " + t("autoDetectProviderFailed", { provider: "Azure DevOps", message: e.message })));