Skip to content

Commit

Permalink
feat(question): add secure option (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
PierreDemailly authored Oct 14, 2023
1 parent 0af1cb2 commit 69f7b67
Show file tree
Hide file tree
Showing 11 changed files with 68 additions and 18 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:

steps:
- name: Harden Runner
uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 # v2.5.1
uses: step-security/harden-runner@1b05615854632b887b69ae1be8cbefe72d3ae423 # v2.6.0
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs

Expand All @@ -54,7 +54,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@ddccb873888234080b77e9bc2d4764d5ccaaccf9 # v2.21.9
uses: github/codeql-action/init@d90b8d79de6dc1f58e83a1499aa58d6c93dc28de # v2.22.2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -68,7 +68,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@ddccb873888234080b77e9bc2d4764d5ccaaccf9 # v2.21.9
uses: github/codeql-action/autobuild@d90b8d79de6dc1f58e83a1499aa58d6c93dc28de # v2.22.2

# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
Expand All @@ -81,6 +81,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@ddccb873888234080b77e9bc2d4764d5ccaaccf9 # v2.21.9
uses: github/codeql-action/analyze@d90b8d79de6dc1f58e83a1499aa58d6c93dc28de # v2.22.2
with:
category: "/language:${{matrix.language}}"
2 changes: 1 addition & 1 deletion .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
fail-fast: false
steps:
- name: Harden Runner
uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 # v2.5.1
uses: step-security/harden-runner@1b05615854632b887b69ae1be8cbefe72d3ae423 # v2.6.0
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:

steps:
- name: Harden Runner
uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 # v2.5.1
uses: step-security/harden-runner@1b05615854632b887b69ae1be8cbefe72d3ae423 # v2.6.0
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs

Expand All @@ -43,7 +43,7 @@ jobs:
persist-credentials: false

- name: "Run analysis"
uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0
uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # v2.3.0
with:
results_file: results.sarif
results_format: sarif
Expand Down Expand Up @@ -73,6 +73,6 @@ jobs:

# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@ddccb873888234080b77e9bc2d4764d5ccaaccf9 # v2.21.9
uses: github/codeql-action/upload-sarif@d90b8d79de6dc1f58e83a1499aa58d6c93dc28de # v2.22.2
with:
sarif_file: results.sarif
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ question(message: string, options?: PromptOptions): Promise<string>
```

Simple prompt, similar to `rl.question()` with an improved UI.
Use `options.secure` if you need to hide both input and answer.
Use `options.validators` to handle user input.

**Example**
Expand Down Expand Up @@ -156,6 +157,7 @@ export interface Validator {
export interface QuestionOptions extends SharedOptions {
defaultValue?: string;
validators?: Validator[];
secure?: boolean;
}

export interface Choice {
Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface Validator {
export interface QuestionOptions extends SharedOptions {
defaultValue?: string;
validators?: Validator[];
secure?: boolean;
}

export interface Choice {
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
"type": "module",
"devDependencies": {
"@nodesecure/eslint-config": "^1.8.0",
"@types/node": "^20.8.2",
"@types/node": "^20.8.5",
"c8": "^8.0.1",
"eslint": "^8.50.0",
"esmock": "^2.5.1"
"eslint": "^8.51.0",
"esmock": "^2.5.2"
},
"dependencies": {
"is-unicode-supported": "^1.3.0",
Expand Down
15 changes: 14 additions & 1 deletion src/abstract-prompt.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Import Node.js Dependencies
import { EOL } from "node:os";
import { createInterface } from "node:readline";
import { Writable } from "node:stream";

// Import Third-party Dependencies
import stripAnsi from "strip-ansi";
Expand All @@ -23,11 +24,23 @@ export class AbstractPrompt {
this.message = message;
this.history = [];
this.agent = PromptAgent.agent();
this.mute = false;

if (this.stdout.isTTY) {
this.stdin.setRawMode(true);
}
this.rl = createInterface({ input, output });
this.rl = createInterface({
input,
output: new Writable({
write: (chunk, encoding, callback) => {
if (!this.mute) {
this.stdout.write(chunk, encoding);
}
callback();
}
}),
terminal: true
});
}

write(data) {
Expand Down
10 changes: 7 additions & 3 deletions src/question-prompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import { SYMBOLS } from "./constants.js";

export class QuestionPrompt extends AbstractPrompt {
#validators;
#secure;

constructor(message, options = {}) {
const {
stdin = process.stdin,
stdout = process.stdout,
defaultValue,
validators = []
validators = [],
secure = false
} = options;

super(message, stdin, stdout);
Expand All @@ -28,6 +30,7 @@ export class QuestionPrompt extends AbstractPrompt {
this.defaultValue = defaultValue;
this.tip = this.defaultValue ? ` (${this.defaultValue})` : "";
this.#validators = validators;
this.#secure = Boolean(secure);
this.questionSuffixError = "";
}

Expand All @@ -37,9 +40,11 @@ export class QuestionPrompt extends AbstractPrompt {

this.rl.question(questionQuery, (answer) => {
this.history.push(questionQuery + answer);
this.mute = false;

resolve(answer);
});
this.mute = this.#secure;
});
}

Expand All @@ -54,8 +59,7 @@ export class QuestionPrompt extends AbstractPrompt {

#writeAnswer() {
const prefix = this.answer ? SYMBOLS.Tick : SYMBOLS.Cross;
const answer = kleur.yellow(this.answer ?? "");

const answer = kleur.yellow(this.#secure ? "CONFIDENTIAL" : this.answer ?? "");
this.write(`${prefix} ${kleur.bold(this.message)} ${SYMBOLS.Pointer} ${answer}${EOL}`);
}

Expand Down
3 changes: 2 additions & 1 deletion test/helpers/mock-process.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export function mockProcess(inputs, writeCb) {
pause: () => true,
paused: () => false,
resume: () => true,
removeListener: () => true
removeListener: () => true,
listenerCount: () => true
};

return { stdout, stdin };
Expand Down
4 changes: 2 additions & 2 deletions test/helpers/testing-prompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { mockProcess } from "./mock-process.js";

export class TestingPrompt {
static async QuestionPrompt(message, options) {
const { input, onStdoutWrite, defaultValue, validators } = options;
const { input, onStdoutWrite, defaultValue, validators, secure } = options;
const inputs = Array.isArray(input) ? input : [input];

const { QuestionPrompt } = await esmock("../../src/question-prompt", { }, {
Expand All @@ -25,7 +25,7 @@ export class TestingPrompt {
});
const { stdin, stdout } = mockProcess([], (data) => onStdoutWrite(data));

return new QuestionPrompt(message, { stdin, stdout, defaultValue, validators });
return new QuestionPrompt(message, { stdin, stdout, defaultValue, validators, secure });
}

static async SelectPrompt(message, options) {
Expand Down
29 changes: 29 additions & 0 deletions test/question-prompt.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,33 @@ describe("QuestionPrompt", () => {
message: "defaultValue must be a string"
});
});

it("should not display answer when prompt is secure", async() => {
const logs = [];
const questionPrompt = await TestingPrompt.QuestionPrompt("What's your name?", {
input: ["John Deeoe"],
secure: true,
onStdoutWrite: (log) => logs.push(log)
});
const input = await questionPrompt.question();

assert.equal(input, "John Deeoe");
assert.deepStrictEqual(logs, [
"? What's your name?",
"✔ What's your name? › CONFIDENTIAL"
]);
});

it("should not display answer when prompt is secure and using PromptAgent", async() => {
const logs = [];
const { stdin, stdout } = mockProcess([], (text) => logs.push(text));
kPromptAgent.nextAnswer("John Doe");

const input = await question("What's your name?", { secure: true, stdin, stdout });

assert.equal(input, "John Doe");
assert.deepStrictEqual(logs, [
"✔ What's your name? › CONFIDENTIAL"
]);
});
});

0 comments on commit 69f7b67

Please sign in to comment.