pass-manager-cli 1.0.1 → 1.0.2
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 +65 -6
- package/index.js +2 -1
- package/package.json +5 -4
- package/src/commands/passwords.js +11 -4
- package/src/commands/serve.js +24 -0
- package/src/lib/api.js +19 -22
- package/src/lib/commands.js +2 -2
- package/src/lib/storage.js +110 -26
- package/src/services/passwordService.js +50 -8
- package/src/utils/crypto.js +54 -0
- package/docs/API_CONTRACT.md +0 -99
- package/src/commands/host.js +0 -9
- package/src/services/hostService.js +0 -10
package/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# Password Manager CLI (PMC)
|
|
2
2
|
|
|
3
|
-
A command-line interface (CLI) tool for managing your passwords efficiently.
|
|
3
|
+
A secure command-line interface (CLI) tool for managing your passwords efficiently.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
7
|
+
- **End-to-End Encryption**: All passwords are encrypted with **AES-256-GCM** using a Master Password.
|
|
8
|
+
- **System Keychain Integration**: Securely store your Master Password in your OS keychain (macOS, Windows, or Linux) for seamless access.
|
|
9
|
+
- **Interactive Mode**: Securely add passwords via interactive prompts to keep them out of your terminal history.
|
|
9
10
|
- **Clipboard Integration**: Copy passwords directly to your clipboard for quick use.
|
|
10
|
-
- **
|
|
11
|
+
- **Standalone Local Storage**: Your vault is stored locally in `~/.config/pmc/passwords.json`.
|
|
11
12
|
|
|
12
13
|
## Installation
|
|
13
14
|
|
|
@@ -23,6 +24,64 @@ just setup
|
|
|
23
24
|
pmc --help
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
## User Guide
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
### 1. Setup & Adding Passwords
|
|
30
|
+
The safest way to add a password is using the interactive mode. This prevents your password from being saved in your terminal history.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pmc add
|
|
34
|
+
```
|
|
35
|
+
*You will be prompted for the URL, Username, and Password.*
|
|
36
|
+
|
|
37
|
+
### 2. Listing Passwords
|
|
38
|
+
To see all your stored accounts (passwords remain hidden):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pmc list
|
|
42
|
+
```
|
|
43
|
+
*Note the **index** number of the entry you want to use.*
|
|
44
|
+
|
|
45
|
+
### 3. Copying to Clipboard
|
|
46
|
+
To use a password, copy it directly to your clipboard using its index:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pmc copy <index>
|
|
50
|
+
```
|
|
51
|
+
*Example: `pmc copy 0`*
|
|
52
|
+
|
|
53
|
+
### 4. Updating an Entry
|
|
54
|
+
If you change a password or username:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pmc update <index>
|
|
58
|
+
```
|
|
59
|
+
*This will prompt you with the current values, which you can edit or keep.*
|
|
60
|
+
|
|
61
|
+
### 5. Deleting an Entry
|
|
62
|
+
To remove a password permanently:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pmc delete <index>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
### Security Tip: The Master Password
|
|
71
|
+
The first time you use PMC, you'll create a **Master Password**.
|
|
72
|
+
- **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.
|
|
73
|
+
- **Lost Password:** If you lose your Master Password, **your data cannot be recovered**. Keep it safe!
|
|
74
|
+
|
|
75
|
+
## Contribution
|
|
76
|
+
|
|
77
|
+
Contributions are welcome! If you'd like to improve PMC, please follow these steps:
|
|
78
|
+
|
|
79
|
+
1. **Fork the repository** on GitHub.
|
|
80
|
+
2. **Clone your fork** locally: `git clone https://github.com/YOUR_USERNAME/password-manager-cli.git`.
|
|
81
|
+
3. **Create a new branch** for your feature or bugfix: `git checkout -b feature-name`.
|
|
82
|
+
4. **Make your changes** and ensure everything works as expected.
|
|
83
|
+
5. **Commit your changes**: `git commit -m "Add feature name"`.
|
|
84
|
+
6. **Push to your branch**: `git push origin feature-name`.
|
|
85
|
+
7. **Create a Pull Request** on the original repository.
|
|
86
|
+
|
|
87
|
+
Please ensure your code follows the existing style and structure of the project.
|
package/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { init } from "./src/lib/commands.js";
|
|
5
|
+
import { getPasswordByUrl } from "./src/lib/api.js";
|
|
5
6
|
|
|
6
7
|
const program = new Command();
|
|
7
8
|
|
|
@@ -10,4 +11,4 @@ program
|
|
|
10
11
|
.description("CLI for Password Manager")
|
|
11
12
|
.version("1.0.0");
|
|
12
13
|
|
|
13
|
-
init(program);
|
|
14
|
+
init(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pass-manager-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
"pmc": "./index.js"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"
|
|
15
|
+
"@inquirer/prompts": "^8.1.0",
|
|
16
16
|
"clipboardy": "^5.0.2",
|
|
17
17
|
"commander": "^14.0.2",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
18
|
+
"express": "^5.2.1",
|
|
19
|
+
"keytar": "^7.9.0",
|
|
20
|
+
"lowdb": "^7.0.1"
|
|
20
21
|
}
|
|
21
22
|
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
listPasswords,
|
|
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((index) => updatePassword(index));
|
|
40
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { getPasswordByUrl } from "../lib/api.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
|
+
const passwords = await getPasswordByUrl(url);
|
|
17
|
+
res.send(passwords);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const startServer = () => {
|
|
21
|
+
app.listen(PORT, () => {
|
|
22
|
+
console.log(`Server running at http://localhost:${PORT}/`);
|
|
23
|
+
});
|
|
24
|
+
};
|
package/src/lib/api.js
CHANGED
|
@@ -1,28 +1,25 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { getHostIP } from "./storage.js";
|
|
1
|
+
import { addItem, clear, getPasswords, updateItem } from "./storage.js";
|
|
3
2
|
|
|
4
|
-
const
|
|
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
|
-
};
|
|
3
|
+
export const getAllPasswords = async () => await getPasswords();
|
|
11
4
|
|
|
12
|
-
export const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
};
|
|
5
|
+
export const getPasswordByUrl = async (url) =>
|
|
6
|
+
await getPasswords().then((passwords) =>
|
|
7
|
+
passwords.filter((password) => password.url === url)
|
|
8
|
+
);
|
|
17
9
|
|
|
18
10
|
export const createPassword = async (url, username, password) => {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
const passwordMapping = {
|
|
12
|
+
url: url,
|
|
13
|
+
username: username,
|
|
14
|
+
password: password,
|
|
15
|
+
};
|
|
16
|
+
await addItem(passwordMapping);
|
|
22
17
|
};
|
|
23
18
|
|
|
24
|
-
export const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
export const deletePasswordByIndex = async (index) => {
|
|
20
|
+
await clear(index);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const updatePasswordByIndex = async (index, updates) => {
|
|
24
|
+
await updateItem(index, updates);
|
|
25
|
+
};
|
package/src/lib/commands.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { passwordCommands } from "../commands/passwords.js";
|
|
2
|
-
import {
|
|
2
|
+
import { serverCommands } from "../commands/serve.js";
|
|
3
3
|
|
|
4
4
|
export const init = (program) => {
|
|
5
5
|
passwordCommands(program);
|
|
6
|
-
|
|
6
|
+
serverCommands(program);
|
|
7
7
|
program.parse();
|
|
8
8
|
};
|
package/src/lib/storage.js
CHANGED
|
@@ -1,39 +1,123 @@
|
|
|
1
|
-
import nodePersist from "node-persist";
|
|
2
1
|
import os from "os";
|
|
3
2
|
import path from "path";
|
|
3
|
+
import { JSONFilePreset } from "lowdb/node";
|
|
4
|
+
import { password as passwordPrompt, confirm } from "@inquirer/prompts";
|
|
5
|
+
import { encrypt, decrypt } from "../utils/crypto.js";
|
|
6
|
+
import keytar from "keytar";
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
const SERVICE_NAME = "PMC-CLI";
|
|
9
|
+
const ACCOUNT_NAME = "MasterPassword";
|
|
6
10
|
|
|
7
|
-
const
|
|
8
|
-
if (!
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
const getMasterPassword = async (isNew = false) => {
|
|
12
|
+
if (!isNew) {
|
|
13
|
+
const storedPassword = await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
14
|
+
if (storedPassword) {
|
|
15
|
+
return storedPassword;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const masterPassword = await passwordPrompt({
|
|
20
|
+
message: isNew
|
|
21
|
+
? "Create a new Master Password for your vault:"
|
|
22
|
+
: "Enter your Master Password to unlock the vault:",
|
|
23
|
+
mask: "*",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const shouldSave = await confirm({
|
|
27
|
+
message: "Save Master Password to System Keychain?",
|
|
28
|
+
default: true,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (shouldSave) {
|
|
32
|
+
await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, masterPassword);
|
|
33
|
+
console.log("Master Password saved to System Keychain.");
|
|
18
34
|
}
|
|
35
|
+
|
|
36
|
+
return masterPassword;
|
|
19
37
|
};
|
|
20
38
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
39
|
+
const initStorage = async () => {
|
|
40
|
+
const homeDirectory = os.homedir();
|
|
41
|
+
const storageDirectory = path.join(
|
|
42
|
+
homeDirectory,
|
|
43
|
+
".config",
|
|
44
|
+
"pmc",
|
|
45
|
+
"passwords.json"
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const defaultData = { vault: null };
|
|
49
|
+
const db = await JSONFilePreset(storageDirectory, defaultData);
|
|
50
|
+
|
|
51
|
+
return db;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const addItem = async (newPasswordEntry) => {
|
|
55
|
+
const db = await initStorage();
|
|
56
|
+
const isNew = !db.data.vault;
|
|
57
|
+
|
|
58
|
+
const masterPassword = await getMasterPassword(isNew);
|
|
59
|
+
|
|
60
|
+
let passwords = [];
|
|
61
|
+
if (!isNew) {
|
|
62
|
+
passwords = await decrypt(db.data.vault, masterPassword);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
passwords.push(newPasswordEntry);
|
|
66
|
+
|
|
67
|
+
const encryptedVault = await encrypt(passwords, masterPassword);
|
|
68
|
+
await db.update((data) => {
|
|
69
|
+
data.vault = encryptedVault;
|
|
70
|
+
});
|
|
24
71
|
};
|
|
25
72
|
|
|
26
|
-
export const
|
|
27
|
-
await initStorage();
|
|
28
|
-
|
|
73
|
+
export const getPasswords = async () => {
|
|
74
|
+
const db = await initStorage();
|
|
75
|
+
if (!db.data.vault) {
|
|
76
|
+
console.log("Vault is empty.");
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const masterPassword = await getMasterPassword();
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
return await decrypt(db.data.vault, masterPassword);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error("Decryption failed. " + error.message);
|
|
86
|
+
// If decryption fails, maybe the keychain password is wrong/stale?
|
|
87
|
+
// Optionally delete it so the user can re-enter.
|
|
88
|
+
await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
29
91
|
};
|
|
30
92
|
|
|
31
|
-
export const clear = async () => {
|
|
32
|
-
await initStorage();
|
|
33
|
-
|
|
93
|
+
export const clear = async (indexToRemove) => {
|
|
94
|
+
const db = await initStorage();
|
|
95
|
+
if (!db.data.vault) return;
|
|
96
|
+
|
|
97
|
+
const masterPassword = await getMasterPassword();
|
|
98
|
+
let passwords = await decrypt(db.data.vault, masterPassword);
|
|
99
|
+
|
|
100
|
+
if (indexToRemove >= 0 && indexToRemove < passwords.length) {
|
|
101
|
+
passwords.splice(indexToRemove, 1);
|
|
102
|
+
const encryptedVault = await encrypt(passwords, masterPassword);
|
|
103
|
+
await db.update((data) => {
|
|
104
|
+
data.vault = encryptedVault;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
34
107
|
};
|
|
35
108
|
|
|
36
|
-
export const
|
|
37
|
-
await initStorage();
|
|
38
|
-
|
|
39
|
-
|
|
109
|
+
export const updateItem = async (indexToUpdate, updatedEntry) => {
|
|
110
|
+
const db = await initStorage();
|
|
111
|
+
if (!db.data.vault) return;
|
|
112
|
+
|
|
113
|
+
const masterPassword = await getMasterPassword();
|
|
114
|
+
let passwords = await decrypt(db.data.vault, masterPassword);
|
|
115
|
+
|
|
116
|
+
if (indexToUpdate >= 0 && indexToUpdate < passwords.length) {
|
|
117
|
+
passwords[indexToUpdate] = { ...passwords[indexToUpdate], ...updatedEntry };
|
|
118
|
+
const encryptedVault = await encrypt(passwords, masterPassword);
|
|
119
|
+
await db.update((data) => {
|
|
120
|
+
data.vault = encryptedVault;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
};
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
getAllPasswords,
|
|
3
3
|
createPassword,
|
|
4
|
-
|
|
4
|
+
deletePasswordByIndex,
|
|
5
|
+
updatePasswordByIndex,
|
|
5
6
|
} from "../lib/api.js";
|
|
6
7
|
import { copyToClipboard } from "../utils/clipboard.js";
|
|
8
|
+
import { input, password as passwordPrompt } from "@inquirer/prompts";
|
|
7
9
|
|
|
8
10
|
export const addPassword = async (url, username, password) => {
|
|
9
11
|
try {
|
|
10
|
-
|
|
12
|
+
const finalUrl =
|
|
13
|
+
url ||
|
|
14
|
+
(await input({ message: "Enter URL (e.g., google.com):" }));
|
|
15
|
+
const finalUsername =
|
|
16
|
+
username || (await input({ message: "Enter Username:" }));
|
|
17
|
+
const finalPassword =
|
|
18
|
+
password ||
|
|
19
|
+
(await passwordPrompt({ message: "Enter Password:", mask: "*" }));
|
|
20
|
+
|
|
21
|
+
await createPassword(finalUrl, finalUsername, finalPassword);
|
|
11
22
|
console.log("Password added successfully.");
|
|
12
23
|
} catch (error) {
|
|
13
24
|
console.error("Failed to add password:", error.message);
|
|
@@ -16,7 +27,7 @@ export const addPassword = async (url, username, password) => {
|
|
|
16
27
|
|
|
17
28
|
export const listPasswords = async () => {
|
|
18
29
|
try {
|
|
19
|
-
const passwords = await
|
|
30
|
+
const passwords = await getAllPasswords();
|
|
20
31
|
console.table(passwords, ["url", "username"]);
|
|
21
32
|
} catch (error) {
|
|
22
33
|
console.error("Failed to list passwords:", error.message);
|
|
@@ -25,7 +36,7 @@ export const listPasswords = async () => {
|
|
|
25
36
|
|
|
26
37
|
export const copyPassword = async (index) => {
|
|
27
38
|
try {
|
|
28
|
-
const passwords = await
|
|
39
|
+
const passwords = await getAllPasswords();
|
|
29
40
|
|
|
30
41
|
if (index >= 0 && index < passwords.length) {
|
|
31
42
|
const password = passwords[index].password;
|
|
@@ -39,14 +50,45 @@ export const copyPassword = async (index) => {
|
|
|
39
50
|
|
|
40
51
|
export const deletePassword = async (index) => {
|
|
41
52
|
try {
|
|
42
|
-
const passwords = await
|
|
53
|
+
const passwords = await getAllPasswords();
|
|
43
54
|
|
|
44
55
|
if (index >= 0 && index < passwords.length) {
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
await deletePasswordByIndex(index);
|
|
57
|
+
|
|
47
58
|
console.log("Password deleted successfully.");
|
|
48
59
|
} else console.error("Invalid index provided.");
|
|
49
60
|
} catch (error) {
|
|
50
61
|
console.error("Failed to delete password:", error.message);
|
|
51
62
|
}
|
|
52
63
|
};
|
|
64
|
+
|
|
65
|
+
export const updatePassword = async (index) => {
|
|
66
|
+
try {
|
|
67
|
+
const passwords = await getAllPasswords();
|
|
68
|
+
|
|
69
|
+
if (index >= 0 && index < passwords.length) {
|
|
70
|
+
const current = passwords[index];
|
|
71
|
+
const updates = {};
|
|
72
|
+
|
|
73
|
+
const newUsername = await input({
|
|
74
|
+
message: `Enter Username (leave empty to keep: ${current.username}):`,
|
|
75
|
+
});
|
|
76
|
+
if (newUsername) updates.username = newUsername;
|
|
77
|
+
|
|
78
|
+
const newPassword = await passwordPrompt({
|
|
79
|
+
message: "Enter New Password (leave empty to keep current):",
|
|
80
|
+
mask: "*",
|
|
81
|
+
});
|
|
82
|
+
if (newPassword) updates.password = newPassword;
|
|
83
|
+
|
|
84
|
+
if (Object.keys(updates).length > 0) {
|
|
85
|
+
await updatePasswordByIndex(index, updates);
|
|
86
|
+
console.log("Password entry updated successfully.");
|
|
87
|
+
} else {
|
|
88
|
+
console.log("No changes made.");
|
|
89
|
+
}
|
|
90
|
+
} else console.error("Invalid index provided.");
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error("Failed to update password:", error.message);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
|
|
4
|
+
const pbkdf2 = promisify(crypto.pbkdf2);
|
|
5
|
+
|
|
6
|
+
const ALGORITHM = "aes-256-gcm";
|
|
7
|
+
const KEY_LENGTH = 32;
|
|
8
|
+
const SALT_LENGTH = 16;
|
|
9
|
+
const IV_LENGTH = 12;
|
|
10
|
+
const ITERATIONS = 100000;
|
|
11
|
+
|
|
12
|
+
export const encrypt = async (data, password) => {
|
|
13
|
+
const salt = crypto.randomBytes(SALT_LENGTH);
|
|
14
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
15
|
+
|
|
16
|
+
const key = await pbkdf2(password, salt, ITERATIONS, KEY_LENGTH, "sha256");
|
|
17
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
18
|
+
|
|
19
|
+
const encrypted = Buffer.concat([
|
|
20
|
+
cipher.update(JSON.stringify(data), "utf8"),
|
|
21
|
+
cipher.final(),
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const authTag = cipher.getAuthTag();
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
salt: salt.toString("hex"),
|
|
28
|
+
iv: iv.toString("hex"),
|
|
29
|
+
authTag: authTag.toString("hex"),
|
|
30
|
+
encryptedData: encrypted.toString("hex"),
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const decrypt = async (vault, password) => {
|
|
35
|
+
const salt = Buffer.from(vault.salt, "hex");
|
|
36
|
+
const iv = Buffer.from(vault.iv, "hex");
|
|
37
|
+
const authTag = Buffer.from(vault.authTag, "hex");
|
|
38
|
+
const encryptedData = Buffer.from(vault.encryptedData, "hex");
|
|
39
|
+
|
|
40
|
+
const key = await pbkdf2(password, salt, ITERATIONS, KEY_LENGTH, "sha256");
|
|
41
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
42
|
+
|
|
43
|
+
decipher.setAuthTag(authTag);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const decrypted = Buffer.concat([
|
|
47
|
+
decipher.update(encryptedData),
|
|
48
|
+
decipher.final(),
|
|
49
|
+
]);
|
|
50
|
+
return JSON.parse(decrypted.toString("utf8"));
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw new Error("Invalid master password or corrupted data.");
|
|
53
|
+
}
|
|
54
|
+
};
|
package/docs/API_CONTRACT.md
DELETED
|
@@ -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/src/commands/host.js
DELETED
|
@@ -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
|
-
};
|