Skip to content

Commit d6f7dd9

Browse files
Merge pull request #120 from SocketDev/cg/addAuditLogFeature
Add audit log feature
2 parents 80ca958 + fdf1e06 commit d6f7dd9

File tree

5 files changed

+1105
-148
lines changed

5 files changed

+1105
-148
lines changed

cli.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ try {
2020
entry[0] = 'raw-npm'
2121
} else if (entry[0] === 'rawNpx') {
2222
entry[0] = 'raw-npx'
23+
} else if (entry[0] === 'auditlog') {
24+
entry[0] = 'audit-log'
2325
}
2426
return entry
2527
}))

lib/commands/audit-log/index.js

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/* eslint-disable no-console */
2+
import { Separator } from '@inquirer/select'
3+
import chalk from 'chalk'
4+
import inquirer from 'inquirer'
5+
import meow from 'meow'
6+
import ora from 'ora'
7+
8+
import { outputFlags } from '../../flags/index.js'
9+
import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
10+
import { prepareFlags } from '../../utils/flags.js'
11+
import { printFlagList } from '../../utils/formatting.js'
12+
import { FREE_API_KEY, getDefaultKey, setupSdk } from '../../utils/sdk.js'
13+
14+
/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
15+
export const auditlog = {
16+
description: 'Look up the audit log for an organization',
17+
async run (argv, importMeta, { parentName }) {
18+
const name = parentName + ' audit-log'
19+
20+
const input = setupCommand(name, auditlog.description, argv, importMeta)
21+
if (input) {
22+
const spinner = ora(`Looking up audit log for ${input.orgSlug}\n`).start()
23+
await fetchOrgAuditLog(input.orgSlug, input, spinner)
24+
}
25+
}
26+
}
27+
28+
const auditLogFlags = prepareFlags({
29+
type: {
30+
type: 'string',
31+
shortFlag: 't',
32+
default: '',
33+
description: 'Type of log event',
34+
},
35+
perPage: {
36+
type: 'number',
37+
shortFlag: 'pp',
38+
default: 30,
39+
description: 'Results per page - default is 30',
40+
},
41+
page: {
42+
type: 'number',
43+
shortFlag: 'p',
44+
default: 1,
45+
description: 'Page number - default is 1',
46+
}
47+
})
48+
49+
// Internal functions
50+
51+
/**
52+
* @typedef CommandInput
53+
* @property {boolean} outputJson
54+
* @property {boolean} outputMarkdown
55+
* @property {string} orgSlug
56+
* @property {string} type
57+
* @property {number} page
58+
* @property {number} per_page
59+
*/
60+
61+
/**
62+
* @param {string} name
63+
* @param {string} description
64+
* @param {readonly string[]} argv
65+
* @param {ImportMeta} importMeta
66+
* @returns {void|CommandInput}
67+
*/
68+
function setupCommand (name, description, argv, importMeta) {
69+
const flags = {
70+
...auditLogFlags,
71+
...outputFlags
72+
}
73+
74+
const cli = meow(`
75+
Usage
76+
$ ${name} <org slug>
77+
78+
Options
79+
${printFlagList(flags, 6)}
80+
81+
Examples
82+
$ ${name} FakeOrg
83+
`, {
84+
argv,
85+
description,
86+
importMeta,
87+
flags
88+
})
89+
90+
const {
91+
json: outputJson,
92+
markdown: outputMarkdown,
93+
type,
94+
page,
95+
perPage
96+
} = cli.flags
97+
98+
if (cli.input.length < 1) {
99+
console.error(`${chalk.bgRed('Input error')}: Please provide an organization slug \n`)
100+
cli.showHelp()
101+
return
102+
}
103+
const [orgSlug = ''] = cli.input
104+
105+
return {
106+
outputJson,
107+
outputMarkdown,
108+
orgSlug,
109+
type: type && type.charAt(0).toUpperCase() + type.slice(1),
110+
page,
111+
per_page: perPage
112+
}
113+
}
114+
115+
/**
116+
* @typedef AuditLogData
117+
* @property {import('@socketsecurity/sdk').SocketSdkReturnType<'getAuditLogEvents'>["data"]} data
118+
*/
119+
120+
/**
121+
* @param {string} orgSlug
122+
* @param {CommandInput} input
123+
* @param {import('ora').Ora} spinner
124+
* @returns {Promise<void|AuditLogData>}
125+
*/
126+
async function fetchOrgAuditLog (orgSlug, input, spinner) {
127+
const socketSdk = await setupSdk(getDefaultKey() || FREE_API_KEY)
128+
const result = await handleApiCall(socketSdk.getAuditLogEvents(orgSlug, input), `Looking up audit log for ${orgSlug}\n`)
129+
130+
if (!result.success) {
131+
return handleUnsuccessfulApiResponse('getAuditLogEvents', result, spinner)
132+
}
133+
spinner.stop()
134+
135+
const /** @type {({name: string} | Separator)[]} */ data = []
136+
const /** @type {{[key: string]: string}} */ logDetails = {}
137+
138+
result.data.results.map(d => {
139+
data.push({
140+
name: `${d.created_at && new Date(d.created_at).toLocaleDateString('en-us', { year: 'numeric', month: 'numeric', day: 'numeric' })} - ${d.user_email} - ${d.type} - ${d.ip_address} - ${d.user_agent}`
141+
}, new Separator())
142+
143+
logDetails[`${d.created_at && new Date(d.created_at).toLocaleDateString('en-us', { year: 'numeric', month: 'numeric', day: 'numeric' })} - ${d.user_email} - ${d.type} - ${d.ip_address} - ${d.user_agent}`] = JSON.stringify(d.payload)
144+
return data
145+
})
146+
147+
inquirer
148+
.prompt(
149+
{
150+
type: 'list',
151+
name: 'log',
152+
message: input.type ? `\n Audit log for: ${orgSlug} with type: ${input.type} \n` : `\n Audit log for: ${orgSlug} \n`,
153+
choices: data,
154+
pageSize: 30
155+
}
156+
)
157+
.then((/** @type {{log: string}} */ answers) => console.log(logDetails[answers.log]))
158+
159+
return {
160+
data: result.data
161+
}
162+
}

lib/commands/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export * from './raw-npx/index.js'
99
export * from './report/index.js'
1010
export * from './wrapper/index.js'
1111
export * from './scan/index.js'
12+
export * from './audit-log/index.js'

0 commit comments

Comments
 (0)