Skip to content

Commit

Permalink
Add show usage and har file check
Browse files Browse the repository at this point in the history
  • Loading branch information
frontegg-david committed Oct 31, 2023
1 parent 95c3772 commit da47118
Show file tree
Hide file tree
Showing 12 changed files with 126 additions and 81 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,5 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
run: cd ./dist/harmor && npm publish --tag latest
run: cd ./dist/harmor && chmod +x ./src/index.js && && npm publish --tag latest

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "harmor",
"version": "1.0.0",
"version": "1.0.1",
"license": "MIT",
"scripts": {
"build": "nx build",
Expand Down
15 changes: 0 additions & 15 deletions src/har-paths.ts

This file was deleted.

132 changes: 94 additions & 38 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import * as process from 'process';
import * as minimist from 'minimist'
import { isUsingYarn } from './helpers';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { green, bgRed, yellow } from 'kolorist';
import { green, bgRed, yellow, red } from 'kolorist';
import Harmor from './Harmor';
import questioner from './questioner';
import { QuestionerResult } from './questioner/types';
import * as prompts from 'prompts';
import { promptOptions } from './questioner/constants';


const currentNodeVersion = process.versions.node;
Expand All @@ -24,55 +26,110 @@ if (major < 16) {


const argv = minimist<{
template?: string
allCookies?: boolean
cookie?: string;
allHeaders?: boolean
header?: string;
jwt?: boolean;
authorization?: boolean;
contentKey?: string;
urlPathPrefix?: string;
pass?: string
enc?: boolean;
y: boolean;
template?: string;
h?: boolean;
help?: boolean;
}>(process.argv.slice(2), {
string: [ '_' ],
})

const cwd = process.cwd()

function showUsage() {
console.log(`
Usage: npx harmor <path_to_file>
Description:
Harmor is a tool designed to sanitize HAR files.
Options:
--template=<path_to_template> The path to a JSON file containing the template to use for sanitization.
Arguments:
<path_to_file> The path to the HAR file you want to sanitize.
Examples:
npx harmor ./data/domain.com.har
npx harmor --template=./my-template.json ./domain.com.har
For more information, visit https://harmor.dev.
`);
}

async function init() {
const argTemplate = argv.template
const argEncryption = argv.enc !== undefined
const argPassword = argv.pass
const argAllCookies = argv.allCookies
const argCookie = argv.cookie
const argAllHeaders = argv.allHeaders
const argHeader = argv.header
const argJwt = argv.jwt
const argAuthorization = argv.authorization
const argContentKey = argv.contentKey
const argUrlPathPrefix = argv.urlPathPrefix
const argQueryParam = argv.queryParam
const argYesForAll = argv.y ?? false

const isYarn = isUsingYarn()


const filePath = '/Users/davidfrontegg/git/harmor/src/data/test1.har'
const showHelp = argv.help ?? argv.h
const fileRelativePath = argv._


if (showHelp) {
showUsage()
process.exit(1)
}

if (fileRelativePath.length === 0) {
showUsage()
console.log(red('Error: No file specified.'));
process.exit(1)
}

const filePath = path.join(process.cwd(), fileRelativePath[0]);

if (!fs.existsSync(filePath)) {
showUsage()
console.log(red(`Error: File "${filePath}" does not exist.`));
process.exit(1);
}


const dirname = path.dirname(filePath)
const fileName = path.basename(filePath, '.har')

const input = fs.readFileSync(filePath, 'utf8');
const harFile = JSON.parse(input);

const result = await questioner(harFile)

let result: QuestionerResult
if (argTemplate) {
const templateFilePath = path.join(process.cwd(), argTemplate);
if (!fs.existsSync(templateFilePath)) {
showUsage()
console.log(red(`Error: Template file "${templateFilePath}" does not exist.`));
process.exit(1);
}
try {
result = JSON.parse(fs.readFileSync(templateFilePath, 'utf8'));
} catch (err) {
showUsage()
console.log(red(`Error: Template file "${templateFilePath}" is not a valid JSON file.`));
process.exit(1);
}
} else {
result = await questioner(harFile)

const { confirm, templateName } = await prompts([ {
type: 'confirm',
name: 'confirm',
message: 'Do you want to save this template for future use?',
initial: true
}, {
type: 'text',
name: 'templateName',
message: 'Enter template name:',
active: (_, values) => values.confirm,
} ], promptOptions)

if (confirm) {
const name = templateName || 'harmor.template.json'
fs.writeFileSync(path.join(dirname, name), JSON.stringify(result), 'utf8')
}
}

const harmorBuilder = Harmor.Builder()

if (result.encryption ?? argEncryption) {
harmorBuilder.encryption(argPassword)
if (result.encryption) {
harmorBuilder.encryption(result.encryption.password)
}
if (result.allCookies) {
harmorBuilder.allCookies()
Expand All @@ -90,13 +147,12 @@ async function init() {
result.headers.forEach(header => harmorBuilder.header(header))
}


if (result.allQueryParams) {
harmorBuilder.allQueryParams()
}

if (result.queryParams.length > 0) {
result.queryParams.forEach(header => harmorBuilder.header(header))
harmorBuilder.queryParam(result.queryParams)
}

if (result.jwt) {
Expand All @@ -118,7 +174,7 @@ async function init() {

console.log('\n')
fs.writeFileSync(path.join(dirname, `${fileName}.harmored.har`), output, 'utf8')
console.log('🛡 HAR been armored:', green(path.join(cwd, `${fileName}.harmor.har`)))
console.log('🛡 HAR been armored:', green(path.join(dirname, `${fileName}.harmor.har`)))

if (harmor.encryption.enabled) {
console.log('🔑 Encryption password:', bgRed(harmor.encryption.password))
Expand Down
19 changes: 10 additions & 9 deletions src/questioner/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Options } from 'prompts';
import { red } from 'kolorist';

export const commonSensitiveCookies = [
'fe_session', // Used by Frontegg to store stateless session in NextJS SDK.
'fe_refresh_', // Used by Frontegg to store refresh token.
Expand Down Expand Up @@ -28,26 +31,24 @@ export const commonQueryParams = [
'code_verifier', // Used by OAuth2 to store authorization codes.
]
export const commonJsonRestrictedKeys = [
'token',
'refreshToken',
'refresh_token',
'accessToken',
'access_token',
'assertion',
'auth',
'authenticity_token',
'code_challenge',
'client_id',
'client_secret',
'code',
'code_challenge',
'code_verifier',
'email',
'facetID',
'fcParams',
'id_token',
'password',
'serverData',
'state',
'token',
]


export const promptOptions: Options = {
onCancel: () => {
throw new Error(red('✖') + ' Operation cancelled')
}
}
6 changes: 2 additions & 4 deletions src/questioner/contentKeys.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import * as prompts from 'prompts';
import { gray } from 'kolorist';
import { Har } from 'har-format';
import { Choice } from 'prompts';
import { createAutocompleteSuggestion, SuggestionKeys } from './helpers';
import { commonJsonRestrictedKeys } from './constants';
import { commonJsonRestrictedKeys, promptOptions } from './constants';

type ContentKeysQuestionerResult = {
contentKeys: string[];
Expand All @@ -26,7 +24,7 @@ const contentKeysQuestioner = async (): Promise<ContentKeysQuestionerResult> =>
skipLabel: 'Skip json sanitization',
allLabel: 'All defaults'
})
})).key
}, promptOptions)).key
if (lastSelected === SuggestionKeys.all) {
return {
contentKeys: commonJsonRestrictedKeys,
Expand Down
3 changes: 2 additions & 1 deletion src/questioner/cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { gray } from 'kolorist';
import { Har } from 'har-format';
import { Choice } from 'prompts';
import { createAutocompleteSuggestion, SuggestionKeys } from './helpers';
import { promptOptions } from './constants';

type CookieQuestionerResult = {
allCookies: boolean;
Expand Down Expand Up @@ -37,7 +38,7 @@ const cookiesQuestioner = async (harFile: Har): Promise<CookieQuestionerResult>
skipLabel: 'Skip cookies sanitization',
allLabel: 'All cookies'
})
})).cookie
}, promptOptions)).cookie
if (lastSelected === SuggestionKeys.all) {
return {
allCookies: true,
Expand Down
12 changes: 6 additions & 6 deletions src/questioner/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
commonJsonRestrictedKeys,
commonQueryParams,
commonSensitiveCookies,
commonSensitiveHeaders
commonSensitiveHeaders, promptOptions
} from './constants';


Expand All @@ -19,7 +19,7 @@ const defaultsQuestioner = async (result: QuestionerResult): Promise<QuestionerR
choices: commonSensitiveCookies
.filter(cookie => result.cookies.indexOf(cookie) === -1)
.map((title) => ({ title, selected: true, value: title })),
})
}, promptOptions)
result.cookies = [ ...result.cookies, ...cookiesToAdd.cookie ]
}

Expand All @@ -31,7 +31,7 @@ const defaultsQuestioner = async (result: QuestionerResult): Promise<QuestionerR
choices: commonSensitiveHeaders
.filter(cookie => result.headers.indexOf(cookie) === -1)
.map((title) => ({ title, selected: true, value: title })),
})
}, promptOptions)
result.headers = [ ...result.headers, ...headersToAdd.headers ]
console.log('asd')
}
Expand All @@ -44,7 +44,7 @@ const defaultsQuestioner = async (result: QuestionerResult): Promise<QuestionerR
choices: commonQueryParams
.filter(queryParam => result.queryParams.indexOf(queryParam) === -1)
.map((title) => ({ title, selected: true, value: title })),
})
}, promptOptions)
result.queryParams = [ ...result.queryParams, ...queryParamsToAdd.queryParams ]
}

Expand All @@ -56,9 +56,9 @@ const defaultsQuestioner = async (result: QuestionerResult): Promise<QuestionerR
const contentKeysToAdd = await prompts({
type: 'multiselect',
name: 'contentKeys',
message: 'Do you want to add default security "Query Params" ?',
message: 'Do you want to add default security "Content Restricted Keys" ?',
choices: contentKeysChoices,
})
}, promptOptions)
result.contentKeys = [ ...result.contentKeys, ...contentKeysToAdd.contentKeys ]
}

Expand Down
7 changes: 4 additions & 3 deletions src/questioner/general.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as prompts from 'prompts';
import { gray } from 'kolorist';
import Crypto, { EncryptionOptions } from '../crypto';
import { promptOptions } from './constants';

type GeneralQuestionerResult = {
encryption: EncryptionOptions;
Expand Down Expand Up @@ -28,7 +29,7 @@ const generalQuestioner = async (): Promise<GeneralQuestionerResult> => {
title: 'by replace with \'_harmored_\'',
value: false
} ],
})
}, promptOptions)
encryption.enabled = encryptionEnabled;

if (encryptionEnabled) {
Expand All @@ -43,7 +44,7 @@ const generalQuestioner = async (): Promise<GeneralQuestionerResult> => {
}
return true
}
})
}, promptOptions)

encryption.password = encryptionPassword
}
Expand All @@ -54,7 +55,7 @@ const generalQuestioner = async (): Promise<GeneralQuestionerResult> => {
message: 'Do you want to sanitize all JWT by regex?' + gray(' - algorithm and signature will be sanitized'),
instructions: false,
initial: true
})
}, promptOptions)
return {
encryption,
jwt
Expand Down
3 changes: 2 additions & 1 deletion src/questioner/headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { gray } from 'kolorist';
import { Har } from 'har-format';
import { Choice } from 'prompts';
import { createAutocompleteSuggestion, SuggestionKeys } from './helpers';
import { promptOptions } from './constants';

type HeaderQuestionerResult = {
allHeaders: boolean;
Expand Down Expand Up @@ -36,7 +37,7 @@ const headersQuestioner = async (harFile: Har): Promise<HeaderQuestionerResult>
skipLabel: 'Skip headers sanitization',
allLabel: 'All headers'
})
})).header
}, promptOptions)).header
if (lastSelected === SuggestionKeys.all) {
return {
allHeaders: true,
Expand Down
Loading

0 comments on commit da47118

Please sign in to comment.