pass-manager-cli 1.0.1 → 1.0.3

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.
package/README.md CHANGED
@@ -1,13 +1,16 @@
1
1
  # Password Manager CLI (PMC)
2
2
 
3
- A command-line interface (CLI) tool for managing your passwords efficiently. This tool allows you to add, list, copy, and delete passwords, as well as link to your password manager's host.
3
+ A secure command-line interface (CLI) tool for managing your passwords efficiently.
4
+
5
+ [Github Repo Link](https://github.com/Ajinkyap331/password-manager-cli)
4
6
 
5
7
  ## Features
6
8
 
7
- - **Secure Password Management**: Add, list, copy, and delete passwords.
8
- - **Host Configuration**: Easily link your CLI to a password manager backend by specifying its host IP.
9
+ - **End-to-End Encryption**: All passwords are encrypted with **AES-256-GCM** using a Master Password.
10
+ - **System Keychain Integration**: Securely store your Master Password in your OS keychain (macOS, Windows, or Linux) for seamless access.
11
+ - **Interactive Mode**: Securely add passwords via interactive prompts to keep them out of your terminal history.
9
12
  - **Clipboard Integration**: Copy passwords directly to your clipboard for quick use.
10
- - **Modular Structure**: Built with a clear separation of concerns for easy maintenance and extensibility.
13
+ - **Standalone Local Storage**: Your vault is stored locally in `~/.config/pmc/passwords.json`.
11
14
 
12
15
  ## Installation
13
16
 
@@ -23,6 +26,72 @@ just setup
23
26
  pmc --help
24
27
  ```
25
28
 
26
- ### API Contract
29
+ ## User Guide
30
+
31
+ ### 1. Setup & Adding Passwords
32
+ The safest way to add a password is using the interactive mode. This prevents your password from being saved in your terminal history.
33
+
34
+ ```bash
35
+ pmc add
36
+ ```
37
+ *You will be prompted for the URL, Username, and Password.*
38
+
39
+ ### 2. Listing Passwords
40
+ To see all your stored accounts (passwords remain hidden):
41
+
42
+ ```bash
43
+ pmc list
44
+ ```
45
+ *Note the **index** number of the entry you want to use.*
46
+
47
+ ### 3. Copying to Clipboard
48
+ To use a password, copy it directly to your clipboard using its index:
49
+
50
+ ```bash
51
+ pmc copy <index>
52
+ ```
53
+ *Example: `pmc copy 0`*
54
+
55
+ ### 4. Updating an Entry
56
+ If you change a password or username:
57
+
58
+ ```bash
59
+ pmc update <index>
60
+ ```
61
+ *This will prompt you with the current values, which you can edit or keep.*
62
+
63
+ ### 5. Deleting an Entry
64
+ To remove a password permanently:
65
+
66
+ ```bash
67
+ pmc delete <index>
68
+ ```
69
+
70
+ ### 6. Serving via HTTP
71
+ Start a local HTTP server to interact with other applications (e.g., UI or browser extensions).
72
+
73
+ ```bash
74
+ pmc serve
75
+ ```
76
+ *Starts a server at `http://localhost:7474`. You can query passwords via `GET /password?url=<url>`.*
77
+
78
+ ---
79
+
80
+ ### Security Tip: The Master Password
81
+ The first time you use PMC, you'll create a **Master Password**.
82
+ - **Keychain:** We recommend saying "Yes" when asked to save it to the System Keychain. This allows you to use PMC without typing your Master Password every time.
83
+ - **Lost Password:** If you lose your Master Password, **your data cannot be recovered**. Keep it safe!
84
+
85
+ ## Contribution
86
+
87
+ Contributions are welcome! If you'd like to improve PMC, please follow these steps:
88
+
89
+ 1. **[Fork the repository](https://github.com/Ajinkyap331/password-manager-cli)** on GitHub.
90
+ 2. **Clone your fork** locally: `git clone https://github.com/YOUR_USERNAME/password-manager-cli.git`.
91
+ 3. **Create a new branch** for your feature or bugfix: `git checkout -b feature-name`.
92
+ 4. **Make your changes** and ensure everything works as expected.
93
+ 5. **Commit your changes**: `git commit -m "Add feature name"`.
94
+ 6. **Push to your branch**: `git push origin feature-name`.
95
+ 7. **Create a Pull Request** on the original repository.
27
96
 
28
- For details on the backend API endpoints and data structures that this CLI interacts with, please refer to the [API Contract Documentation](https://github.com/Ajinkyap331/password-manager-cli/blob/main/docs/API_CONTRACT.md).
97
+ Please ensure your code follows the existing style and structure of the project.
package/index.js CHANGED
@@ -10,4 +10,4 @@ program
10
10
  .description("CLI for Password Manager")
11
11
  .version("1.0.0");
12
12
 
13
- init(program);
13
+ init(program);
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "pass-manager-cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
- "scripts": {},
7
+ "scripts": {
8
+ "test": "node --test tests/*.test.js"
9
+ },
8
10
  "keywords": [],
9
11
  "author": "",
10
12
  "license": "ISC",
@@ -12,10 +14,11 @@
12
14
  "pmc": "./index.js"
13
15
  },
14
16
  "dependencies": {
15
- "axios": "^1.13.2",
17
+ "@inquirer/prompts": "^8.1.0",
16
18
  "clipboardy": "^5.0.2",
17
19
  "commander": "^14.0.2",
18
- "inquirer": "^13.1.0",
19
- "node-persist": "^4.0.4"
20
+ "express": "^5.2.1",
21
+ "keytar": "^7.9.0",
22
+ "lowdb": "^7.0.1"
20
23
  }
21
24
  }
@@ -1,8 +1,9 @@
1
1
  import {
2
- addPassword,
3
2
  listPasswords,
3
+ addPassword,
4
4
  copyPassword,
5
5
  deletePassword,
6
+ updatePassword,
6
7
  } from "../services/passwordService.js";
7
8
 
8
9
  export const passwordCommands = (program) => {
@@ -14,9 +15,9 @@ export const passwordCommands = (program) => {
14
15
  program
15
16
  .command("add")
16
17
  .description("add a password")
17
- .argument("url", "url")
18
- .argument("username", "username")
19
- .argument("password", "password")
18
+ .argument("[url]", "url")
19
+ .argument("[username]", "username")
20
+ .argument("[password]", "password")
20
21
  .action(addPassword);
21
22
 
22
23
  program
@@ -30,4 +31,10 @@ export const passwordCommands = (program) => {
30
31
  .description("Delete Password")
31
32
  .argument("index", "index")
32
33
  .action(deletePassword);
33
- };
34
+
35
+ program
36
+ .command("update")
37
+ .description("Update password entry")
38
+ .argument("index", "index")
39
+ .action(updatePassword);
40
+ };
@@ -0,0 +1,33 @@
1
+ import express from "express";
2
+ import { getPasswords } from "../lib/storage.js";
3
+ export const app = express();
4
+ const PORT = 7474;
5
+
6
+ export const serverCommands = (program) => {
7
+ program
8
+ .command("serve")
9
+ .description("Create a server to interact with UI")
10
+ .action(startServer);
11
+ };
12
+
13
+ app.get("/password", async (req, res) => {
14
+ const url = req.query.url;
15
+ console.log(url);
16
+ try {
17
+ const allPasswords = await getPasswords();
18
+ const passwords = allPasswords.filter((password) => password.url === url);
19
+ res.send(passwords);
20
+ } catch (error) {
21
+ res.status(500).send({ error: error.message });
22
+ }
23
+ });
24
+
25
+ const startServer = async () => {
26
+ try {
27
+ app.listen(PORT, "127.0.0.1", () => {
28
+ console.log(`Server running at http://127.0.0.1:${PORT}/`);
29
+ });
30
+ } catch (error) {
31
+ console.error("Failed to start server:", error.message);
32
+ }
33
+ };
package/src/config.js ADDED
@@ -0,0 +1,15 @@
1
+ import os from "os";
2
+ import path from "path";
3
+
4
+ export const SERVICE_NAME = "PMC-CLI";
5
+ export const ACCOUNT_NAME = "MasterPassword";
6
+
7
+ export const HOME_DIRECTORY = os.homedir();
8
+ export const STORAGE_DIRECTORY = path.join(
9
+ HOME_DIRECTORY,
10
+ ".config",
11
+ "pmc",
12
+ "passwords.json"
13
+ );
14
+
15
+ export const DEFAULT_DATA = { vault: null };
@@ -1,8 +1,8 @@
1
1
  import { passwordCommands } from "../commands/passwords.js";
2
- import { hostCommands } from "../commands/host.js";
2
+ import { serverCommands } from "../commands/serve.js";
3
3
 
4
4
  export const init = (program) => {
5
5
  passwordCommands(program);
6
- hostCommands(program);
6
+ serverCommands(program);
7
7
  program.parse();
8
8
  };
@@ -1,39 +1,113 @@
1
- import nodePersist from "node-persist";
2
- import os from "os";
3
- import path from "path";
1
+ import { password as passwordPrompt, confirm } from "@inquirer/prompts";
2
+ import { vaultEncryption } from "../utils/crypto.js";
3
+ import keytar from "keytar";
4
+ import {
5
+ getMasterPasswordPrompt,
6
+ saveToKeychainPrompt,
7
+ } from "../utils/prompts.js";
8
+ import { JSONFilePreset } from "lowdb/node";
9
+ import {
10
+ SERVICE_NAME,
11
+ ACCOUNT_NAME,
12
+ STORAGE_DIRECTORY,
13
+ DEFAULT_DATA,
14
+ } from "../config.js";
15
+ import { DecryptionError } from "../utils/errors.js";
4
16
 
5
- let isInitialized = false;
17
+ const getMasterPassword = async (isNew = false) => {
18
+ if (!isNew) {
19
+ const storedPassword = await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
20
+ if (storedPassword) {
21
+ return storedPassword;
22
+ }
23
+ }
6
24
 
7
- const initStorage = async () => {
8
- if (!isInitialized) {
9
- const homeDirectory = os.homedir();
10
- const storageDirectory = path.join(homeDirectory, ".config", "pmc");
11
-
12
- await nodePersist.init({
13
- dir: storageDirectory,
14
- logging: false,
15
- ttl: false,
16
- });
17
- isInitialized = true;
25
+ const masterPassword = await passwordPrompt(getMasterPasswordPrompt(isNew));
26
+
27
+ const shouldSave = await confirm(saveToKeychainPrompt);
28
+
29
+ if (shouldSave) {
30
+ await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, masterPassword);
31
+ console.log("Master Password saved to System Keychain.");
18
32
  }
33
+
34
+ return masterPassword;
19
35
  };
20
36
 
21
- export const setItem = async (key, value) => {
22
- await initStorage();
23
- return nodePersist.setItem(key, value);
37
+ const initStorage = async () => {
38
+ const db = await JSONFilePreset(STORAGE_DIRECTORY, DEFAULT_DATA);
39
+ return db;
24
40
  };
25
41
 
26
- export const getItem = async (key) => {
27
- await initStorage();
28
- return nodePersist.getItem(key);
42
+ export const addItem = async (newPasswordEntry) => {
43
+ const db = await initStorage();
44
+ const isNew = !db.data.vault;
45
+
46
+ const masterPassword = await getMasterPassword(isNew);
47
+
48
+ let passwords = [];
49
+ if (!isNew) {
50
+ passwords = await vaultEncryption.decrypt(db.data.vault, masterPassword);
51
+ }
52
+
53
+ passwords.push(newPasswordEntry);
54
+
55
+ const encryptedVault = await vaultEncryption.encrypt(passwords, masterPassword);
56
+ await db.update((data) => {
57
+ data.vault = encryptedVault;
58
+ });
29
59
  };
30
60
 
31
- export const clear = async () => {
32
- await initStorage();
33
- return nodePersist.clear();
61
+ export const getPasswords = async () => {
62
+ const db = await initStorage();
63
+ if (!db.data.vault) {
64
+ console.log("Vault is empty.");
65
+ return [];
66
+ }
67
+
68
+ const masterPassword = await getMasterPassword();
69
+
70
+ try {
71
+ return await vaultEncryption.decrypt(db.data.vault, masterPassword);
72
+ } catch (error) {
73
+ if (error instanceof DecryptionError) {
74
+ await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
75
+ throw new DecryptionError(
76
+ "Decryption failed. The master password may be incorrect or the vault is corrupted. Stored keychain password has been cleared."
77
+ );
78
+ }
79
+ throw error;
80
+ }
34
81
  };
35
82
 
36
- export const getHostIP = async () => {
37
- await initStorage();
38
- return nodePersist.getItem("host");
39
- };
83
+ export const clear = async (indexToRemove) => {
84
+ const db = await initStorage();
85
+ if (!db.data.vault) return;
86
+
87
+ const masterPassword = await getMasterPassword();
88
+ let passwords = await vaultEncryption.decrypt(db.data.vault, masterPassword);
89
+
90
+ if (indexToRemove >= 0 && indexToRemove < passwords.length) {
91
+ passwords.splice(indexToRemove, 1);
92
+ const encryptedVault = await vaultEncryption.encrypt(passwords, masterPassword);
93
+ await db.update((data) => {
94
+ data.vault = encryptedVault;
95
+ });
96
+ }
97
+ };
98
+
99
+ export const updateItem = async (indexToUpdate, updatedEntry) => {
100
+ const db = await initStorage();
101
+ if (!db.data.vault) return;
102
+
103
+ const masterPassword = await getMasterPassword();
104
+ let passwords = await vaultEncryption.decrypt(db.data.vault, masterPassword);
105
+
106
+ if (indexToUpdate >= 0 && indexToUpdate < passwords.length) {
107
+ passwords[indexToUpdate] = { ...passwords[indexToUpdate], ...updatedEntry };
108
+ const encryptedVault = await vaultEncryption.encrypt(passwords, masterPassword);
109
+ await db.update((data) => {
110
+ data.vault = encryptedVault;
111
+ });
112
+ }
113
+ };
@@ -1,37 +1,59 @@
1
+ import { input, password as passwordPrompt } from "@inquirer/prompts";
1
2
  import {
2
3
  getPasswords,
3
- createPassword,
4
- deletePasswordById,
5
- } from "../lib/api.js";
4
+ addItem,
5
+ clear,
6
+ updateItem,
7
+ } from "../lib/storage.js";
6
8
  import { copyToClipboard } from "../utils/clipboard.js";
9
+ import {
10
+ urlPrompt,
11
+ usernamePrompt,
12
+ passwordPromptConfig,
13
+ getUpdateUsernamePrompt,
14
+ updatePasswordPromptConfig,
15
+ } from "../utils/prompts.js";
7
16
 
8
- export const addPassword = async (url, username, password) => {
17
+ export const listPasswords = async () => {
9
18
  try {
10
- await createPassword(url, username, password);
11
- console.log("Password added successfully.");
19
+ const passwords = await getPasswords();
20
+ console.table(passwords, ["url", "username"]);
12
21
  } catch (error) {
13
- console.error("Failed to add password:", error.message);
22
+ console.error("Failed to list passwords:", error.message);
14
23
  }
15
24
  };
16
25
 
17
- export const listPasswords = async () => {
26
+ export const addPassword = async (url, username, password) => {
18
27
  try {
19
- const passwords = await getPasswords();
20
- console.table(passwords, ["url", "username"]);
28
+ const finalUrl = url || (await input(urlPrompt));
29
+ const finalUsername = username || (await input(usernamePrompt));
30
+ const finalPassword =
31
+ password || (await passwordPrompt(passwordPromptConfig));
32
+
33
+ const newPasswordEntry = {
34
+ url: finalUrl,
35
+ username: finalUsername,
36
+ password: finalPassword,
37
+ };
38
+ await addItem(newPasswordEntry);
39
+ console.log("Password added successfully.");
21
40
  } catch (error) {
22
- console.error("Failed to list passwords:", error.message);
41
+ console.error("Failed to add password:", error.message);
23
42
  }
24
43
  };
25
44
 
26
45
  export const copyPassword = async (index) => {
27
46
  try {
28
47
  const passwords = await getPasswords();
48
+ const i = parseInt(index, 10);
29
49
 
30
- if (index >= 0 && index < passwords.length) {
31
- const password = passwords[index].password;
50
+ if (!isNaN(i) && i >= 0 && i < passwords.length) {
51
+ const password = passwords[i].password;
32
52
  const success = await copyToClipboard(password);
33
53
  if (success) console.log("Password copied to clipboard.");
34
- } else console.error("Invalid index provided.");
54
+ } else {
55
+ console.error("Invalid index provided.");
56
+ }
35
57
  } catch (error) {
36
58
  console.error("Failed to copy password:", error.message);
37
59
  }
@@ -40,13 +62,46 @@ export const copyPassword = async (index) => {
40
62
  export const deletePassword = async (index) => {
41
63
  try {
42
64
  const passwords = await getPasswords();
65
+ const i = parseInt(index, 10);
43
66
 
44
- if (index >= 0 && index < passwords.length) {
45
- const passwordId = passwords[index].id;
46
- await deletePasswordById(passwordId);
67
+ if (!isNaN(i) && i >= 0 && i < passwords.length) {
68
+ await clear(i);
47
69
  console.log("Password deleted successfully.");
48
- } else console.error("Invalid index provided.");
70
+ } else {
71
+ console.error("Invalid index provided.");
72
+ }
49
73
  } catch (error) {
50
74
  console.error("Failed to delete password:", error.message);
51
75
  }
52
76
  };
77
+
78
+ export const updatePassword = async (index) => {
79
+ try {
80
+ const passwords = await getPasswords();
81
+ const i = parseInt(index, 10);
82
+
83
+ if (!isNaN(i) && i >= 0 && i < passwords.length) {
84
+ const current = passwords[i];
85
+ const updates = {};
86
+
87
+ const newUsername = await input(
88
+ getUpdateUsernamePrompt(current.username)
89
+ );
90
+ if (newUsername) updates.username = newUsername;
91
+
92
+ const newPassword = await passwordPrompt(updatePasswordPromptConfig);
93
+ if (newPassword) updates.password = newPassword;
94
+
95
+ if (Object.keys(updates).length > 0) {
96
+ await updateItem(i, updates);
97
+ console.log("Password entry updated successfully.");
98
+ } else {
99
+ console.log("No changes made.");
100
+ }
101
+ } else {
102
+ console.error("Invalid index provided.");
103
+ }
104
+ } catch (error) {
105
+ console.error("Failed to update password:", error.message);
106
+ }
107
+ };
@@ -0,0 +1,73 @@
1
+ import crypto from "crypto";
2
+ import { promisify } from "util";
3
+ import { DecryptionError } from "./errors.js";
4
+
5
+ const pbkdf2 = promisify(crypto.pbkdf2);
6
+
7
+ export class VaultEncryption {
8
+ constructor() {
9
+ this.ALGORITHM = "aes-256-gcm";
10
+ this.KEY_LENGTH = 32;
11
+ this.SALT_LENGTH = 16;
12
+ this.IV_LENGTH = 12;
13
+ this.ITERATIONS = 100000;
14
+ }
15
+
16
+ async encrypt(data, password) {
17
+ const salt = crypto.randomBytes(this.SALT_LENGTH);
18
+ const iv = crypto.randomBytes(this.IV_LENGTH);
19
+
20
+ const key = await pbkdf2(
21
+ password,
22
+ salt,
23
+ this.ITERATIONS,
24
+ this.KEY_LENGTH,
25
+ "sha256"
26
+ );
27
+ const cipher = crypto.createCipheriv(this.ALGORITHM, key, iv);
28
+
29
+ const encrypted = Buffer.concat([
30
+ cipher.update(JSON.stringify(data), "utf8"),
31
+ cipher.final(),
32
+ ]);
33
+
34
+ const authTag = cipher.getAuthTag();
35
+
36
+ return {
37
+ salt: salt.toString("hex"),
38
+ iv: iv.toString("hex"),
39
+ authTag: authTag.toString("hex"),
40
+ encryptedData: encrypted.toString("hex"),
41
+ };
42
+ }
43
+
44
+ async decrypt(vault, password) {
45
+ const salt = Buffer.from(vault.salt, "hex");
46
+ const iv = Buffer.from(vault.iv, "hex");
47
+ const authTag = Buffer.from(vault.authTag, "hex");
48
+ const encryptedData = Buffer.from(vault.encryptedData, "hex");
49
+
50
+ const key = await pbkdf2(
51
+ password,
52
+ salt,
53
+ this.ITERATIONS,
54
+ this.KEY_LENGTH,
55
+ "sha256"
56
+ );
57
+ const decipher = crypto.createDecipheriv(this.ALGORITHM, key, iv);
58
+
59
+ decipher.setAuthTag(authTag);
60
+
61
+ try {
62
+ const decrypted = Buffer.concat([
63
+ decipher.update(encryptedData),
64
+ decipher.final(),
65
+ ]);
66
+ return JSON.parse(decrypted.toString("utf8"));
67
+ } catch (error) {
68
+ throw new DecryptionError("Invalid master password or corrupted data.");
69
+ }
70
+ }
71
+ }
72
+
73
+ export const vaultEncryption = new VaultEncryption();
@@ -0,0 +1,24 @@
1
+ export class AppError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = this.constructor.name;
5
+ }
6
+ }
7
+
8
+ export class DecryptionError extends AppError {
9
+ constructor(message = "Decryption failed.") {
10
+ super(message);
11
+ }
12
+ }
13
+
14
+ export class KeychainError extends AppError {
15
+ constructor(message = "Keychain operation failed.") {
16
+ super(message);
17
+ }
18
+ }
19
+
20
+ export class StorageError extends AppError {
21
+ constructor(message = "Storage operation failed.") {
22
+ super(message);
23
+ }
24
+ }
@@ -0,0 +1,26 @@
1
+ export const urlPrompt = { message: "Enter URL (e.g., google.com):" };
2
+
3
+ export const usernamePrompt = { message: "Enter Username:" };
4
+
5
+ export const passwordPromptConfig = { message: "Enter Password:", mask: "*" };
6
+
7
+ export const getUpdateUsernamePrompt = (currentUsername) => ({
8
+ message: `Enter Username (leave empty to keep: ${currentUsername}):`,
9
+ });
10
+
11
+ export const updatePasswordPromptConfig = {
12
+ message: "Enter New Password (leave empty to keep current):",
13
+ mask: "*",
14
+ };
15
+
16
+ export const getMasterPasswordPrompt = (isNew) => ({
17
+ message: isNew
18
+ ? "Create a new Master Password for your vault:"
19
+ : "Enter your Master Password to unlock the vault:",
20
+ mask: "*",
21
+ });
22
+
23
+ export const saveToKeychainPrompt = {
24
+ message: "Save Master Password to System Keychain?",
25
+ default: true,
26
+ };
@@ -0,0 +1,47 @@
1
+ import { vaultEncryption } from "../src/utils/crypto.js";
2
+ import { DecryptionError } from "../src/utils/errors.js";
3
+ import assert from "node:assert";
4
+ import test from "node:test";
5
+
6
+ test("VaultEncryption - encrypt and decrypt", async (t) => {
7
+ const data = { secret: "my-password-123" };
8
+ const password = "master-password";
9
+
10
+ await t.test("should successfully encrypt and then decrypt data", async () => {
11
+ const encrypted = await vaultEncryption.encrypt(data, password);
12
+
13
+ assert.ok(encrypted.salt, "should have salt");
14
+ assert.ok(encrypted.iv, "should have iv");
15
+ assert.ok(encrypted.authTag, "should have authTag");
16
+ assert.ok(encrypted.encryptedData, "should have encryptedData");
17
+
18
+ const decrypted = await vaultEncryption.decrypt(encrypted, password);
19
+ assert.deepStrictEqual(decrypted, data, "decrypted data should match original data");
20
+ });
21
+
22
+ await t.test("should throw DecryptionError with incorrect password", async () => {
23
+ const encrypted = await vaultEncryption.encrypt(data, password);
24
+
25
+ await assert.rejects(
26
+ async () => {
27
+ await vaultEncryption.decrypt(encrypted, "wrong-password");
28
+ },
29
+ {
30
+ name: "DecryptionError",
31
+ message: "Invalid master password or corrupted data.",
32
+ }
33
+ );
34
+ });
35
+
36
+ await t.test("should throw error with corrupted data", async () => {
37
+ const encrypted = await vaultEncryption.encrypt(data, password);
38
+ const corruptedVault = { ...encrypted, encryptedData: "corrupted" };
39
+
40
+ await assert.rejects(
41
+ async () => {
42
+ await vaultEncryption.decrypt(corruptedVault, password);
43
+ },
44
+ DecryptionError
45
+ );
46
+ });
47
+ });
@@ -1,99 +0,0 @@
1
- # Backend API Contract for Password Manager CLI
2
-
3
- This document outlines the API endpoints, methods, and data structures expected from the backend service that the Password Manager CLI (PMC) interacts with.
4
-
5
- **Base URL Structure:** `http://<hostIP>:5421/`
6
-
7
- The `<hostIP>` is configured using the `pmc host link <IP_ADDRESS>` command.
8
-
9
- ---
10
-
11
- ## 1. Passwords API
12
-
13
- This API is responsible for managing password entries.
14
-
15
- ### a. `GET /passwords` - Retrieve All Passwords
16
-
17
- Retrieves a list of all password entries stored in the backend.
18
-
19
- * **Method:** `GET`
20
- * **Path:** `/passwords`
21
- * **Authentication:** (Assumed to be handled by backend, not explicitly defined in current CLI)
22
- * **Request Headers:** (None explicitly sent by CLI for now, could include Authorization in future)
23
- * **Request Body:** (None)
24
- * **Response:** `200 OK` - A JSON array of password objects.
25
-
26
- ```json
27
- [
28
- {
29
- "id": "uuid-v4-string-1",
30
- "url": "example.com",
31
- "username": "user@example.com",
32
- "password": "secure_password_1"
33
- },
34
- {
35
- "id": "uuid-v4-string-2",
36
- "url": "another.com",
37
- "username": "another_user",
38
- "password": "secure_password_2"
39
- },
40
- // ... more password objects
41
- ]
42
- ```
43
-
44
- ### b. `POST /passwords` - Add a New Password
45
-
46
- Adds a new password entry to the backend.
47
-
48
- * **Method:** `POST`
49
- * **Path:** `/passwords`
50
- * **Authentication:** (Assumed)
51
- * **Request Headers:**
52
- * `Content-Type: application/json`
53
- * **Request Body:** A JSON object containing the new password's details.
54
-
55
- ```json
56
- {
57
- "url": "new-site.com",
58
- "username": "new_user",
59
- "password": "new_secure_password"
60
- }
61
- ```
62
- * **Response:** `200 OK` or `201 Created` - The newly created password object, including its generated `id`.
63
-
64
- ```json
65
- {
66
- "id": "uuid-v4-string-3",
67
- "url": "new-site.com",
68
- "username": "new_user",
69
- "password": "new_secure_password"
70
- }
71
- ```
72
-
73
- ### c. `DELETE /passwords/:id` - Delete a Password by ID
74
-
75
- Deletes a specific password entry from the backend using its unique ID.
76
-
77
- * **Method:** `DELETE`
78
- * **Path:** `/passwords/{id}` (where `{id}` is the unique identifier of the password)
79
- * **Authentication:** (Assumed)
80
- * **Request Headers:** (None)
81
- * **Request Body:** (None)
82
- * **Response:** `200 OK` (or `204 No Content`) - Indicates successful deletion. No content is typically returned for 204.
83
-
84
- ```json
85
- // Example for 200 OK
86
- {
87
- "message": "Password deleted successfully"
88
- }
89
- ```
90
- or
91
- (No Content for 204)
92
-
93
- ---
94
-
95
- ## 2. Host Configuration
96
-
97
- The `pmc host link <IP_ADDRESS>` command primarily configures the local CLI's storage to remember the backend server's IP address. There is no explicit "Host API" endpoint for this action in the backend. The `hostIP` is used by the CLI to construct the base URL for the Passwords API.
98
-
99
- ---
package/mise.toml DELETED
@@ -1,2 +0,0 @@
1
- [tools]
2
- node = "24"
@@ -1,9 +0,0 @@
1
- import { linkManager } from "../services/hostService.js";
2
-
3
- export const hostCommands = (program) => {
4
- program
5
- .command("link")
6
- .description("link a password manager")
7
- .argument("ip", "ip")
8
- .action(linkManager);
9
- };
package/src/lib/api.js DELETED
@@ -1,28 +0,0 @@
1
- import axios from "axios";
2
- import { getHostIP } from "./storage.js";
3
-
4
- const getBaseUrl = async () => {
5
- const hostIP = await getHostIP();
6
- if (!hostIP) {
7
- throw new Error("Host IP not set. Please link a password manager first.");
8
- }
9
- return `http://${hostIP}:5421/passwords`;
10
- };
11
-
12
- export const getPasswords = async () => {
13
- const url = await getBaseUrl();
14
- const response = await axios.get(url);
15
- return response.data;
16
- };
17
-
18
- export const createPassword = async (url, username, password) => {
19
- const baseUrl = await getBaseUrl();
20
- const response = await axios.post(baseUrl, { url, username, password });
21
- return response.data;
22
- };
23
-
24
- export const deletePasswordById = async (id) => {
25
- const baseUrl = await getBaseUrl();
26
- const response = await axios.delete(`${baseUrl}/${id}`);
27
- return response.data;
28
- };
@@ -1,10 +0,0 @@
1
- import { setItem } from "../lib/storage.js";
2
-
3
- export const linkManager = async (ip) => {
4
- try {
5
- await setItem("host", ip);
6
- console.log(`Host IP ${ip} linked successfully.`);
7
- } catch (error) {
8
- console.error("Failed to link host IP:", error.message);
9
- }
10
- };