Skip to content

Commit

Permalink
update js object detection result to contain record; add more error h…
Browse files Browse the repository at this point in the history
…andling in import from env; update method and type names
  • Loading branch information
muntaxir4 committed Dec 23, 2024
1 parent 376f296 commit 8b28a7c
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 60 deletions.
4 changes: 2 additions & 2 deletions apps/cli/src/commands/project.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import CreateProject from './project/create.project'
import DeleteProject from './project/delete.project'
import ForkProject from './project/fork.project'
import GetProject from './project/get.project'
import ImportEnvs from './project/import.project'
import ImportFromEnv from './project/import.project'
import ListProjectForks from './project/list-forks.project'
import ListProject from './project/list.project'
import SyncProject from './project/sync.project'
Expand All @@ -30,7 +30,7 @@ export default class ProjectCommand extends BaseCommand {
new SyncProject(),
new UnlinkProject(),
new UpdateProject(),
new ImportEnvs()
new ImportFromEnv()
]
}
}
56 changes: 37 additions & 19 deletions apps/cli/src/commands/project/import.project.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {
CommandActionData,
CommandArgument
CommandArgument,
CommandOption
} from '@/types/command/command.types'
import BaseCommand from '../base.command'
import { confirm, text } from '@clack/prompts'
Expand All @@ -11,7 +12,7 @@ import path from 'node:path'
import dotenv from 'dotenv'
import secretDetector from '@keyshade/secret-scan'

export default class ImportEnvs extends BaseCommand {
export default class ImportFromEnv extends BaseCommand {
getName(): string {
return 'import'
}
Expand All @@ -25,9 +26,15 @@ export default class ImportEnvs extends BaseCommand {
{
name: '<Project Slug>',
description: 'Slug of the project where envs will be imported.'
},
}
]
}

getOptions(): CommandOption[] {
return [
{
name: '<.env file path>',
short: '-f',
long: '--env-file <string>',
description: 'Path to the .env file'
}
]
Expand All @@ -37,29 +44,38 @@ export default class ImportEnvs extends BaseCommand {
return true
}

async action({ args }: CommandActionData): Promise<void> {
const [projectSlug, dotEnvPath] = args
async action({ args, options }: CommandActionData): Promise<void> {
const [projectSlug] = args
const { envFile } = options

try {
const envFileContent = await fs.readFile(
path.resolve(dotEnvPath),
'utf-8'
)
const resolvedPath = path.resolve(envFile)
const exists = await fs
.access(resolvedPath)
.then(() => true)
.catch(() => false)
if (!exists) {
throw new Error(`The .env file does not exist at path: ${resolvedPath}`)
}
const envFileContent = await fs.readFile(resolvedPath, 'utf-8')

const envVariables = dotenv.parse(envFileContent)
if (Object.keys(envVariables).length === 0) {
throw new Error('No environment variables found in the provided file')
}

const secretsAndVariables = secretDetector.detectObject(envVariables)
const secretsAndVariables = secretDetector.detectJsObject(envVariables)

Logger.info(
'Detected secrets:\n' +
secretsAndVariables.secrets
.map((secret) => secret[0] + ' = ' + secret[1])
Object.entries(secretsAndVariables.secrets)
.map(([key, value]) => key + ' = ' + value)

Check failure on line 72 in apps/cli/src/commands/project/import.project.ts

View workflow job for this annotation

GitHub Actions / Validate CLI

Invalid operand for a '+' operation. Operands must each be a number or string, allowing a string + any of: `any`, `boolean`, `null`, `RegExp`, `undefined`. Got `unknown`

Check failure on line 72 in apps/cli/src/commands/project/import.project.ts

View workflow job for this annotation

GitHub Actions / Validate CLI

'value' will evaluate to '[object Object]' when stringified
.join('\n')
)
Logger.info(
'Detected variables:\n' +
secretsAndVariables.variables
.map((variable) => variable[0] + ' = ' + variable[1])
Object.entries(secretsAndVariables.variables)
.map(([key, value]) => key + ' = ' + value)

Check failure on line 78 in apps/cli/src/commands/project/import.project.ts

View workflow job for this annotation

GitHub Actions / Validate CLI

Invalid operand for a '+' operation. Operands must each be a number or string, allowing a string + any of: `any`, `boolean`, `null`, `RegExp`, `undefined`. Got `unknown`

Check failure on line 78 in apps/cli/src/commands/project/import.project.ts

View workflow job for this annotation

GitHub Actions / Validate CLI

'value' will evaluate to '[object Object]' when stringified
.join('\n')
)

Expand All @@ -85,8 +101,8 @@ export default class ImportEnvs extends BaseCommand {
let noOfSecrets = 0
let noOfVariables = 0
const errors: string[] = []
for (const [key, value] of secretsAndVariables.secrets) {
const { data, error, success } =
for (const [key, value] of Object.entries(secretsAndVariables.secrets)) {
const { error, success } =
await ControllerInstance.getInstance().secretController.createSecret(
{
projectSlug,
Expand All @@ -110,8 +126,10 @@ export default class ImportEnvs extends BaseCommand {
}
}

for (const [key, value] of secretsAndVariables.variables) {
const { data, error, success } =
for (const [key, value] of Object.entries(
secretsAndVariables.variables
)) {
const { error, success } =
await ControllerInstance.getInstance().variableController.createVariable(
{
projectSlug,
Expand Down
19 changes: 12 additions & 7 deletions packages/secret-scan/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import denylist from '@/denylist'
import type { SecretResult, ScanObjectResult } from '@/types'
import type { SecretResult, ScanJsObjectResult } from '@/types'

export type SecretConfig = Record<string, RegExp[]>

Expand All @@ -23,17 +23,22 @@ class SecretDetector {
return { found: false }
}

detectObject(input: Record<string, string>): ScanObjectResult {
const result = {
secrets: [],
variables: []
/**
* Detects if a given js object contains any secret patterns.
* @param input - The object to scan for secret patterns.
* @returns A `ScanJsObjectResult` object containing the secrets and variables found in the object.
*/
detectJsObject(input: Record<string, string>): ScanJsObjectResult {
const result: ScanJsObjectResult = {
secrets: {},
variables: {}
}
for (const [key, value] of Object.entries(input)) {
const secretResult = this.detect(value)
if (secretResult.found) {
result.secrets.push([key, value])
result.secrets[key] = value
} else {
result.variables.push([key, value])
result.variables[key] = value
}
}

Expand Down
58 changes: 29 additions & 29 deletions packages/secret-scan/src/test/detect-js-object.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,24 @@ describe('Dectect Secrets and Variables from Object', () => {
GOOGLE_ANALYTICS: 'UA-123456789-1',
API_PORT: '3000'
}
const result = secretDetector.detectObject(input)
expect(result.secrets).toEqual([
['GITHUB_KEY', input.GITHUB_KEY],
['AWS_KEY', input.AWS_KEY],
['OPENAI_KEY', input.OPENAI_KEY]
])
expect(result.variables).toEqual([
['NEXT_PUBLIC_API_KEY', input.NEXT_PUBLIC_API_KEY],
['GOOGLE_ANALYTICS', input.GOOGLE_ANALYTICS],
['API_PORT', input.API_PORT]
])
const result = secretDetector.detectJsObject(input)
expect(result.secrets).toEqual({
GITHUB_KEY: input.GITHUB_KEY,
AWS_KEY: input.AWS_KEY,
OPENAI_KEY: input.OPENAI_KEY
})
expect(result.variables).toEqual({
NEXT_PUBLIC_API_KEY: input.NEXT_PUBLIC_API_KEY,
GOOGLE_ANALYTICS: input.GOOGLE_ANALYTICS,
API_PORT: input.API_PORT
})
})

it('should return empty arrays for secrets and variables when input is empty', () => {
it('should return empty objects for secrets and variables when input is empty', () => {
const input = {}
const result = secretDetector.detectObject(input)
expect(result.secrets).toEqual([])
expect(result.variables).toEqual([])
const result = secretDetector.detectJsObject(input)
expect(result.secrets).toEqual({})
expect(result.variables).toEqual({})
})

it('should return only variables when there are no secrets', () => {
Expand All @@ -37,13 +37,13 @@ describe('Dectect Secrets and Variables from Object', () => {
GOOGLE_ANALYTICS: 'UA-123456789-1',
API_PORT: '3000'
}
const result = secretDetector.detectObject(input)
expect(result.secrets).toEqual([])
expect(result.variables).toEqual([
['NEXT_PUBLIC_API_KEY', input.NEXT_PUBLIC_API_KEY],
['GOOGLE_ANALYTICS', input.GOOGLE_ANALYTICS],
['API_PORT', input.API_PORT]
])
const result = secretDetector.detectJsObject(input)
expect(result.secrets).toEqual({})
expect(result.variables).toEqual({
NEXT_PUBLIC_API_KEY: input.NEXT_PUBLIC_API_KEY,
GOOGLE_ANALYTICS: input.GOOGLE_ANALYTICS,
API_PORT: input.API_PORT
})
})

it('should return only secrets when there are no variables', () => {
Expand All @@ -52,12 +52,12 @@ describe('Dectect Secrets and Variables from Object', () => {
AWS_KEY: aws.testcases[0].input,
OPENAI_KEY: openAI.testcases[0].input
}
const result = secretDetector.detectObject(input)
expect(result.secrets).toEqual([
['GITHUB_KEY', input.GITHUB_KEY],
['AWS_KEY', input.AWS_KEY],
['OPENAI_KEY', input.OPENAI_KEY]
])
expect(result.variables).toEqual([])
const result = secretDetector.detectJsObject(input)
expect(result.secrets).toEqual({
GITHUB_KEY: input.GITHUB_KEY,
AWS_KEY: input.AWS_KEY,
OPENAI_KEY: input.OPENAI_KEY
})
expect(result.variables).toEqual({})
})
})
6 changes: 3 additions & 3 deletions packages/secret-scan/src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface SecretResult {
regex?: RegExp
}

export interface ScanObjectResult {
secrets: string[][] // string[] -> [key, value]
variables: string[][] // string[] -> [key, value]
export interface ScanJsObjectResult {
secrets: Record<string, string>
variables: Record<string, string>
}

0 comments on commit 8b28a7c

Please sign in to comment.